ソースを参照

Writing first PHP unit tests. Breaking JS unit test classes into separate files

main
Rocketsoup 1年前
コミット
30cfa0b4d3

+ 278
- 0
jstest/BlockTests.js ファイルの表示

@@ -0,0 +1,278 @@
1
+class BlockTests extends BaseTest {
2
+	/** @type {Markdown} */
3
+	parser;
4
+	md(markdown) {
5
+		return normalizeWhitespace(this.parser.toHTML(markdown));
6
+	}
7
+
8
+	setUp() {
9
+		this.parser = Markdown.completeParser;
10
+	}
11
+
12
+	test_paragraphs() {
13
+		let markdown = "Lorem ipsum\n\nDolor sit amet";
14
+		let expected = "<p>Lorem ipsum</p> <p>Dolor sit amet</p>";
15
+		let actual = this.md(markdown);
16
+		this.assertEqual(actual, expected);
17
+	}
18
+
19
+	test_paragraph_lineGrouping() {
20
+		let markdown = "Lorem ipsum\ndolor sit amet";
21
+		let expected = "Lorem ipsum dolor sit amet";
22
+		let actual = this.md(markdown);
23
+		this.assertEqual(actual, expected);
24
+	}
25
+
26
+	test_header_underlineH1() {
27
+		let markdown = "Header 1\n===\n\nLorem ipsum";
28
+		let expected = "<h1>Header 1</h1> <p>Lorem ipsum</p>";
29
+		let actual = this.md(markdown);
30
+		this.assertEqual(actual, expected);
31
+	}
32
+
33
+	test_header_underlineH2() {
34
+		let markdown = "Header 2\n---\n\nLorem ipsum";
35
+		let expected = "<h2>Header 2</h2> <p>Lorem ipsum</p>";
36
+		let actual = this.md(markdown);
37
+		this.assertEqual(actual, expected);
38
+	}
39
+
40
+	test_header_hash() {
41
+		let markdown = "# Header 1\n## Header 2\n### Header 3\n#### Header 4\n##### Header 5\n###### Header 6\n";
42
+		let expected = '<h1>Header 1</h1> <h2>Header 2</h2> <h3>Header 3</h3> <h4>Header 4</h4> <h5>Header 5</h5> <h6>Header 6</h6>';
43
+		let actual = this.md(markdown);
44
+		this.assertEqual(actual, expected);
45
+	}
46
+
47
+	test_header_hash_trailing() {
48
+		let markdown = "# Header 1 #\n## Header 2 ##\n### Header 3 ######";
49
+		let expected = '<h1>Header 1</h1> <h2>Header 2</h2> <h3>Header 3</h3>';
50
+		let actual = this.md(markdown);
51
+		this.assertEqual(actual, expected);
52
+	}
53
+
54
+	test_unorderedList() {
55
+		let markdown = "* Lorem\n* Ipsum\n* Dolor";
56
+		let expected = '<ul> <li>Lorem</li> <li>Ipsum</li> <li>Dolor</li> </ul>';
57
+		let actual = this.md(markdown);
58
+		this.assertEqual(actual, expected);
59
+	}
60
+
61
+	test_unorderedList_nested() {
62
+		let markdown = "* Lorem\n + Ipsum\n* Dolor";
63
+		let expected = '<ul> <li>Lorem<ul> <li>Ipsum</li> </ul> </li> <li>Dolor</li> </ul>';
64
+		let actual = this.md(markdown);
65
+		this.assertEqual(actual, expected);
66
+	}
67
+
68
+	test_unorderedList_hitch() {
69
+		// This incomplete bulleted list locked up the browser at one
70
+		// point, not forever but a REALLY long time
71
+		this.profile(1.0, () => {
72
+			let markdown = "Testing\n\n* ";
73
+			let expected = '<p>Testing</p> <ul> <li></li> </ul>';
74
+			let actual = this.md(markdown);
75
+			this.assertEqual(actual, expected);
76
+		});
77
+	}
78
+
79
+	test_orderedList() {
80
+		let markdown = "1. Lorem\n1. Ipsum\n5. Dolor";
81
+		let expected = '<ol> <li>Lorem</li> <li>Ipsum</li> <li>Dolor</li> </ol>';
82
+		let actual = this.md(markdown);
83
+		this.assertEqual(actual, expected);
84
+	}
85
+
86
+	test_orderedList_numbering() {
87
+		let markdown = "4. Lorem\n1. Ipsum\n9. Dolor";
88
+		let expected = '<ol start="4"> <li>Lorem</li> <li>Ipsum</li> <li>Dolor</li> </ol>';
89
+		let actual = this.md(markdown);
90
+		this.assertEqual(actual, expected);
91
+	}
92
+
93
+	test_orderedList_nested1() {
94
+		let markdown = "1. Lorem\n 1. Ipsum\n1. Dolor";
95
+		let expected = '<ol> <li>Lorem<ol> <li>Ipsum</li> </ol> </li> <li>Dolor</li> </ol>';
96
+		let actual = this.md(markdown);
97
+		this.assertEqual(actual, expected);
98
+	}
99
+
100
+	test_orderedList_nested2() {
101
+		let markdown = "1. Lorem\n 1. Ipsum\n      1. Dolor\n 1. Sit\n1. Amet";
102
+		let expected = '<ol> <li>Lorem<ol> <li>Ipsum<ol> <li>Dolor</li> </ol> </li> <li>Sit</li> </ol> </li> <li>Amet</li> </ol>';
103
+		let actual = this.md(markdown);
104
+		this.assertEqual(actual, expected);
105
+	}
106
+
107
+	test_blockquote() {
108
+		let markdown = '> Lorem ipsum dolor';
109
+		let expected = '<blockquote>Lorem ipsum dolor</blockquote>';
110
+		let actual = this.md(markdown);
111
+		this.assertEqual(actual, expected);
112
+	}
113
+
114
+	test_blockquote_paragraphs() {
115
+		let markdown = '> Lorem ipsum dolor\n>\n>Sit amet';
116
+		let expected = '<blockquote> <p>Lorem ipsum dolor</p> <p>Sit amet</p> </blockquote>';
117
+		let actual = this.md(markdown);
118
+		this.assertEqual(actual, expected);
119
+	}
120
+
121
+	test_blockquote_list() {
122
+		let markdown = '> 1. Lorem\n> 2. Ipsum';
123
+		let expected = '<blockquote> <ol> <li>Lorem</li> <li>Ipsum</li> </ol> </blockquote>';
124
+		let actual = this.md(markdown);
125
+		this.assertEqual(actual, expected);
126
+	}
127
+
128
+	test_codeBlock_indented() {
129
+		let markdown = "Code\n\n    function foo() {\n        return 'bar';\n    }\n\nend";
130
+		let expected = "<p>Code</p>\n\n<pre><code>function foo() {\n    return 'bar';\n}</code></pre>\n\n<p>end</p>";
131
+		let actual = this.parser.toHTML(markdown).trim(); // don't normalize whitespace
132
+		this.assertEqual(actual.replace(/ /g, '⎵'), expected.replace(/ /g, '⎵'));
133
+	}
134
+
135
+	test_codeBlock_fenced() {
136
+		let markdown = "Code\n\n```\nfunction foo() {\n    return 'bar';\n}\n```\n\nend";
137
+		let expected = "<p>Code</p>\n\n<pre><code>function foo() {\n    return 'bar';\n}</code></pre>\n\n<p>end</p>";
138
+		let actual = this.parser.toHTML(markdown).trim(); // don't normalize whitespace
139
+		this.assertEqual(actual.replace(/ /g, '⎵'), expected.replace(/ /g, '⎵'));
140
+	}
141
+
142
+	test_codeBlock_fenced_language() {
143
+		let markdown = "Code\n\n```javascript\nfunction foo() {\n    return 'bar';\n}\n```\n\nend";
144
+		let expected = "<p>Code</p>\n\n<pre><code class=\"language-javascript\">function foo() {\n    return 'bar';\n}</code></pre>\n\n<p>end</p>";
145
+		let actual = this.parser.toHTML(markdown).trim(); // don't normalize whitespace
146
+		this.assertEqual(actual.replace(/ /g, '⎵'), expected.replace(/ /g, '⎵'));
147
+	}
148
+
149
+	test_horizontalRule() {
150
+		let markdown = "Before\n\n---\n\n- - -\n\n***\n\n* * * * * * *\n\nafter";
151
+		let expected = "<p>Before</p> <hr> <hr> <hr> <hr> <p>after</p>";
152
+		let actual = this.md(markdown);
153
+		this.assertEqual(actual, expected);
154
+	}
155
+
156
+	test_table_unfenced() {
157
+		let markdown = "Column A | Column B | Column C\n--- | --- | ---\n1 | 2 | 3\n4 | 5 | 6";
158
+		let expected = "<table> <thead> <tr> <th>Column A</th> <th>Column B</th> <th>Column C</th> </tr> </thead> <tbody> <tr> <td>1</td> <td>2</td> <td>3</td> </tr> <tr> <td>4</td> <td>5</td> <td>6</td> </tr> </tbody> </table>";
159
+		let actual = this.md(markdown);
160
+		this.assertEqual(actual, expected);
161
+	}
162
+
163
+	test_table_fenced() {
164
+		let markdown = "| Column A | Column B | Column C |\n| --- | --- | --- |\n| 1 | 2 | 3\n4 | 5 | 6 |";
165
+		let expected = "<table> <thead> <tr> <th>Column A</th> <th>Column B</th> <th>Column C</th> </tr> </thead> <tbody> <tr> <td>1</td> <td>2</td> <td>3</td> </tr> <tr> <td>4</td> <td>5</td> <td>6</td> </tr> </tbody> </table>";
166
+		let actual = this.md(markdown);
167
+		this.assertEqual(actual, expected);
168
+	}
169
+
170
+	test_table_alignment() {
171
+		let markdown = 'Column A | Column B | Column C\n' +
172
+			':--- | :---: | ---:\n' +
173
+			'1 | 2 | 3\n' +
174
+			'4 | 5 | 6';
175
+		let expected = '<table> ' +
176
+			'<thead> ' +
177
+			'<tr> ' +
178
+			'<th style="text-align: left;">Column A</th> ' +
179
+			'<th style="text-align: center;">Column B</th> ' +
180
+			'<th style="text-align: right;">Column C</th> ' +
181
+			'</tr> ' +
182
+			'</thead> ' +
183
+			'<tbody> ' +
184
+			'<tr> ' +
185
+			'<td style="text-align: left;">1</td> ' +
186
+			'<td style="text-align: center;">2</td> ' +
187
+			'<td style="text-align: right;">3</td> ' +
188
+			'</tr> ' +
189
+			'<tr> ' +
190
+			'<td style="text-align: left;">4</td> ' +
191
+			'<td style="text-align: center;">5</td> ' +
192
+			'<td style="text-align: right;">6</td> ' +
193
+			'</tr> ' +
194
+			'</tbody> ' +
195
+			'</table>';
196
+		let actual = this.md(markdown);
197
+		this.assertEqual(actual, expected);
198
+	}
199
+
200
+	test_table_holes() {
201
+		let markdown = 'Column A||Column C\n' +
202
+			'---|---|---\n' +
203
+			'|1|2||\n' +
204
+			'|4||6|\n' +
205
+			'||8|9|';
206
+		let expected = '<table> ' +
207
+			'<thead> ' +
208
+			'<tr> ' +
209
+			'<th>Column A</th> ' +
210
+			'<th></th> ' +
211
+			'<th>Column C</th> ' +
212
+			'</tr> ' +
213
+			'</thead> ' +
214
+			'<tbody> ' +
215
+			'<tr> ' +
216
+			'<td>1</td> ' +
217
+			'<td>2</td> ' +
218
+			'<td></td> ' +
219
+			'</tr> ' +
220
+			'<tr> ' +
221
+			'<td>4</td> ' +
222
+			'<td></td> ' +
223
+			'<td>6</td> ' +
224
+			'</tr> ' +
225
+			'<tr> ' +
226
+			'<td></td> ' +
227
+			'<td>8</td> ' +
228
+			'<td>9</td> ' +
229
+			'</tr> ' +
230
+			'</tbody> ' +
231
+			'</table>';
232
+		let actual = this.md(markdown);
233
+		this.assertEqual(actual, expected);
234
+	}
235
+
236
+	test_definitionList() {
237
+		let markdown = 'term\n' +
238
+			': definition\n' +
239
+			'another' +
240
+			' term\n' +
241
+			': def 1\n' +
242
+			' broken on next line\n' +
243
+			': def 2';
244
+		let expected = '<dl> ' +
245
+			'<dt>term</dt> ' +
246
+			'<dd>definition</dd> ' +
247
+			'<dt>another term</dt> ' +
248
+			'<dd>def 1 broken on next line</dd> ' +
249
+			'<dd>def 2</dd> ' +
250
+			'</dl>';
251
+		let actual = this.md(markdown);
252
+		this.assertEqual(actual, expected);
253
+	}
254
+
255
+	test_footnotes() {
256
+		let markdown = 'Lorem ipsum[^1] dolor[^abc] sit[^1] amet\n\n[^1]: A footnote\n[^abc]: Another footnote';
257
+		let expected = '<p>Lorem ipsum<sup class="footnote" id="footnoteref_1"><a href="#footnote_1">1</a></sup> ' +
258
+			'dolor<sup class="footnote" id="footnoteref_2"><a href="#footnote_2">2</a></sup> ' +
259
+			'sit<sup class="footnote" id="footnoteref_3"><a href="#footnote_1">1</a></sup> amet</p> ' +
260
+			'<div class="footnotes">' +
261
+			'<ol>' +
262
+			'<li value="1" id="footnote_1">A footnote <a href="#footnoteref_1" class="footnote-backref">↩︎</a> <a href="#footnoteref_3" class="footnote-backref">↩︎</a></li> ' +
263
+			'<li value="2" id="footnote_2">Another footnote <a href="#footnoteref_2" class="footnote-backref">↩︎</a></li> ' +
264
+			'</ol>' +
265
+			'</div>';
266
+		let actual = this.md(markdown);
267
+		this.assertEqual(actual, expected);
268
+	}
269
+
270
+	test_abbreviations() {
271
+		let markdown = 'Lorem ipsum HTML dolor HTML sit\n' +
272
+			'\n' +
273
+			'*[HTML]: Hypertext Markup Language';
274
+		let expected = '<p>Lorem ipsum <abbr title="Hypertext Markup Language">HTML</abbr> dolor <abbr title="Hypertext Markup Language">HTML</abbr> sit</p>';
275
+		let actual = this.md(markdown);
276
+		this.assertEqual(actual, expected);
277
+	}
278
+}

+ 237
- 0
jstest/InlineTests.js ファイルの表示

@@ -0,0 +1,237 @@
1
+class InlineTests extends BaseTest {
2
+	/** @type {Markdown} */
3
+	parser;
4
+	md(markdown) {
5
+		return normalizeWhitespace(this.parser.toHTML(markdown));
6
+	}
7
+
8
+	setUp() {
9
+		this.parser = Markdown.completeParser;
10
+	}
11
+
12
+	test_simpleText() {
13
+		let markdown = 'Lorem ipsum';
14
+		let expected = 'Lorem ipsum';
15
+		let actual = this.md(markdown);
16
+		this.assertEqual(actual, expected);
17
+	}
18
+
19
+	test_strong() {
20
+		let markdown = 'Lorem **ipsum** dolor **sit**';
21
+		let expected = 'Lorem <strong>ipsum</strong> dolor <strong>sit</strong>';
22
+		let actual = this.md(markdown);
23
+		this.assertEqual(actual, expected);
24
+	}
25
+
26
+	test_emphasis() {
27
+		let markdown = 'Lorem _ipsum_ dolor _sit_';
28
+		let expected = 'Lorem <em>ipsum</em> dolor <em>sit</em>';
29
+		let actual = this.md(markdown);
30
+		this.assertEqual(actual, expected);
31
+	}
32
+
33
+	test_strongEmphasis_cleanNesting1() {
34
+		let markdown = 'Lorem **ipsum *dolor* sit** amet';
35
+		let expected = 'Lorem <strong>ipsum <em>dolor</em> sit</strong> amet';
36
+		let actual = this.md(markdown);
37
+		this.assertEqual(actual, expected);
38
+	}
39
+
40
+	test_strongEmphasis_cleanNesting2() {
41
+		let markdown = 'Lorem *ipsum **dolor** sit* amet';
42
+		let expected = 'Lorem <em>ipsum <strong>dolor</strong> sit</em> amet';
43
+		let actual = this.md(markdown);
44
+		this.assertEqual(actual, expected);
45
+	}
46
+
47
+	test_strongEmphasis_tightNesting() {
48
+		let markdown = 'Lorem ***ipsum*** dolor';
49
+		let expected1 = 'Lorem <strong><em>ipsum</em></strong> dolor';
50
+		let expected2 = 'Lorem <em><strong>ipsum</strong></em> dolor';
51
+		let actual = this.md(markdown);
52
+		this.assertTrue(actual == expected1 || actual == expected2);
53
+	}
54
+
55
+	test_strongEmphasis_lopsidedNesting1() {
56
+		let markdown = 'Lorem ***ipsum* dolor** sit';
57
+		let expected = 'Lorem <strong><em>ipsum</em> dolor</strong> sit';
58
+		let actual = this.md(markdown);
59
+		this.assertEqual(actual, expected);
60
+	}
61
+
62
+	test_strongEmphasis_lopsidedNesting2() {
63
+		let markdown = 'Lorem ***ipsum** dolor* sit';
64
+		let expected = 'Lorem <em><strong>ipsum</strong> dolor</em> sit';
65
+		let actual = this.md(markdown);
66
+		this.assertEqual(actual, expected);
67
+	}
68
+
69
+	test_strongEmphasis_lopsidedNesting3() {
70
+		let markdown = 'Lorem **ipsum *dolor*** sit';
71
+		let expected = 'Lorem <strong>ipsum <em>dolor</em></strong> sit';
72
+		let actual = this.md(markdown);
73
+		this.assertEqual(actual, expected);
74
+	}
75
+
76
+	test_strongEmphasis_lopsidedNesting4() {
77
+		let markdown = 'Lorem *ipsum **dolor*** sit';
78
+		let expected = 'Lorem <em>ipsum <strong>dolor</strong></em> sit';
79
+		let actual = this.md(markdown);
80
+		this.assertEqual(actual, expected);
81
+	}
82
+
83
+	test_inlineCode() {
84
+		let markdown = 'Lorem `ipsum` dolor';
85
+		let expected = 'Lorem <code>ipsum</code> dolor';
86
+		let actual = this.md(markdown);
87
+		this.assertEqual(actual, expected);
88
+	}
89
+
90
+	test_inlineCode_withInnerBacktick() {
91
+		let markdown = 'Lorem ``ip`su`m`` dolor';
92
+		let expected = 'Lorem <code>ip`su`m</code> dolor';
93
+		let actual = this.md(markdown);
94
+		this.assertEqual(actual, expected);
95
+	}
96
+
97
+	test_strikethrough_double() {
98
+		let markdown = 'Lorem ~~ipsum~~ dolor';
99
+		let expected = 'Lorem <s>ipsum</s> dolor';
100
+		let actual = this.md(markdown);
101
+		this.assertEqual(actual, expected);
102
+	}
103
+
104
+	test_subscript() {
105
+		let markdown = 'H~2~O';
106
+		let expected = 'H<sub>2</sub>O';
107
+		let actual = this.md(markdown);
108
+		this.assertEqual(actual, expected);
109
+	}
110
+
111
+	test_superscript() {
112
+		let markdown = 'E=mc^2^';
113
+		let expected = 'E=mc<sup>2</sup>';
114
+		let actual = this.md(markdown);
115
+		this.assertEqual(actual, expected);
116
+	}
117
+
118
+	test_highlight() {
119
+		let markdown = 'Lorem ==ipsum== dolor';
120
+		let expected = 'Lorem <mark>ipsum</mark> dolor';
121
+		let actual = this.md(markdown);
122
+		this.assertEqual(actual, expected);
123
+	}
124
+
125
+	test_underline() {
126
+		let markdown = 'Lorem __ipsum__ dolor';
127
+		let expected = 'Lorem <u>ipsum</u> dolor';
128
+		let actual = this.md(markdown);
129
+		this.assertEqual(actual, expected);
130
+	}
131
+
132
+	test_link_fullyQualified() {
133
+		let markdown = 'Lorem [ipsum](https://example.com/path/page.html) dolor';
134
+		let expected = 'Lorem <a href="https://example.com/path/page.html">ipsum</a> dolor';
135
+		let actual = this.md(markdown);
136
+		this.assertEqual(actual, expected);
137
+	}
138
+
139
+	test_link_relative() {
140
+		let markdown = 'Lorem [ipsum](page.html) dolor';
141
+		let expected = 'Lorem <a href="page.html">ipsum</a> dolor';
142
+		let actual = this.md(markdown);
143
+		this.assertEqual(actual, expected);
144
+	}
145
+
146
+	test_link_title() {
147
+		let markdown = 'Lorem [ipsum](page.html "link title") dolor';
148
+		let expected = 'Lorem <a href="page.html" title="link title">ipsum</a> dolor';
149
+		let actual = this.md(markdown);
150
+		this.assertEqual(actual, expected);
151
+	}
152
+
153
+	test_link_literal() {
154
+		let markdown = 'Lorem <https://example.com> dolor';
155
+		let expected = 'Lorem <a href="https://example.com">https://example.com</a> dolor';
156
+		let actual = this.md(markdown);
157
+		this.assertEqual(actual, expected);
158
+	}
159
+
160
+	test_link_ref() {
161
+		let markdown = "Lorem [ipsum][ref] dolor\n\n[ref]: https://example.com";
162
+		let expected = '<p>Lorem <a href="https://example.com">ipsum</a> dolor</p>';
163
+		let actual = this.md(markdown);
164
+		this.assertEqual(actual, expected);
165
+	}
166
+
167
+	test_link_email() {
168
+		let markdown = 'Lorem [ipsum](user@example.com) dolor';
169
+		let expected = 'Lorem <a href="mailto:&#117;&#115;&#101;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;">ipsum</a> dolor';
170
+		let actual = this.md(markdown);
171
+		this.assertEqual(actual, expected);
172
+	}
173
+
174
+	test_link_email_withTitle() {
175
+		let markdown = 'Lorem [ipsum](user@example.com "title") dolor';
176
+		let expected = 'Lorem <a href="mailto:&#117;&#115;&#101;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;" title="title">ipsum</a> dolor';
177
+		let actual = this.md(markdown);
178
+		this.assertEqual(actual, expected);
179
+	}
180
+
181
+	test_link_literalEmail() {
182
+		let markdown = 'Lorem <user@example.com> dolor';
183
+		let expected = 'Lorem <a href="mailto:&#117;&#115;&#101;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;">&#117;&#115;&#101;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;</a> dolor';
184
+		let actual = this.md(markdown);
185
+		this.assertEqual(actual, expected);
186
+	}
187
+
188
+	test_link_image() {
189
+		let markdown = 'Lorem [![alt](image.jpg)](page.html) ipsum';
190
+		let expected = 'Lorem <a href="page.html"><img src="image.jpg" alt="alt"></a> ipsum';
191
+		let actual = this.md(markdown);
192
+		this.assertEqual(actual, expected);
193
+	}
194
+
195
+	test_link_image_complex() {
196
+		let markdown = 'Lorem [![alt] (image.jpg "image title")] (page.html "link title") ipsum';
197
+		let expected = 'Lorem <a href="page.html" title="link title"><img src="image.jpg" alt="alt" title="image title"></a> ipsum';
198
+		let actual = this.md(markdown);
199
+		this.assertEqual(actual, expected);
200
+	}
201
+
202
+	test_image() {
203
+		let markdown = 'Lorem ![alt text](image.jpg) dolor';
204
+		let expected = 'Lorem <img src="image.jpg" alt="alt text"> dolor';
205
+		let actual = this.md(markdown);
206
+		this.assertEqual(actual, expected);
207
+	}
208
+
209
+	test_image_noAlt() {
210
+		let markdown = 'Lorem ![](image.jpg) dolor';
211
+		let expected = 'Lorem <img src="image.jpg"> dolor';
212
+		let actual = this.md(markdown);
213
+		this.assertEqual(actual, expected);
214
+	}
215
+
216
+	test_image_withTitle() {
217
+		let markdown = 'Lorem ![alt text](image.jpg "image title") dolor';
218
+		let expected = 'Lorem <img src="image.jpg" alt="alt text" title="image title"> dolor';
219
+		let actual = this.md(markdown);
220
+		this.assertEqual(actual, expected);
221
+	}
222
+
223
+	test_image_ref() {
224
+		let markdown = 'Lorem ![alt text][ref] dolor\n\n' +
225
+			'[ref]: image.jpg "image title"';
226
+		let expected = '<p>Lorem <img src="image.jpg" alt="alt text" title="image title"> dolor</p>';
227
+		let actual = this.md(markdown);
228
+		this.assertEqual(actual, expected);
229
+	}
230
+
231
+	test_htmlTags() {
232
+		let markdown = 'Lorem <strong title=\'with " quote\' id="with \' apostrophe" lang=unquoted translate forbidden="true">ipsum</strong> dolor';
233
+		let expected = 'Lorem <strong title="with &quot; quote" id="with \' apostrophe" lang="unquoted" translate>ipsum</strong> dolor';
234
+		let actual = this.md(markdown);
235
+		this.assertEqual(actual, expected);
236
+	}
237
+}

+ 99
- 0
jstest/TokenTests.js ファイルの表示

@@ -0,0 +1,99 @@
1
+class TokenTests extends BaseTest {
2
+	test_findFirstTokens() {
3
+		const tokens = [
4
+			new MDToken('Lorem', MDTokenType.Text),
5
+			new MDToken(' ', MDTokenType.Whitespace),
6
+			new MDToken('_', MDTokenType.Underscore),
7
+			new MDToken('ipsum', MDTokenType.Text),
8
+			new MDToken('_', MDTokenType.Underscore),
9
+			new MDToken(' ', MDTokenType.Whitespace),
10
+			new MDToken('dolor', MDTokenType.Text),
11
+			new MDToken('_', MDTokenType.Underscore),
12
+			new MDToken('sit', MDTokenType.Text),
13
+			new MDToken('_', MDTokenType.Underscore),
14
+		];
15
+		const pattern = [
16
+			MDTokenType.Underscore,
17
+		];
18
+		const result = MDToken.findFirstTokens(tokens, pattern);
19
+		const expected = {
20
+			tokens: [ tokens[2] ],
21
+			index: 2,
22
+		};
23
+		this.assertEqual(result, expected);
24
+	}
25
+
26
+	test_findFirstTokens_optionalWhitespace1() {
27
+		const tokens = [
28
+			new MDToken('Lorem', MDTokenType.Text),
29
+			new MDToken(' ', MDTokenType.Whitespace),
30
+			new MDToken('[ipsum]', MDTokenType.Label, 'ipsum'),
31
+			new MDToken('(link.html)', MDTokenType.URL, 'link.html'),
32
+			new MDToken(' ', MDTokenType.Whitespace),
33
+			new MDToken('dolor', MDTokenType.Text),
34
+		];
35
+		const pattern = [
36
+			MDTokenType.Label,
37
+			MDTokenType.META_OptionalWhitespace,
38
+			MDTokenType.URL,
39
+		];
40
+		const result = MDToken.findFirstTokens(tokens, pattern);
41
+		const expected = {
42
+			tokens: [ tokens[2], tokens[3] ],
43
+			index: 2,
44
+		};
45
+		this.assertEqual(result, expected);
46
+	}
47
+
48
+	test_findFirstTokens_optionalWhitespace2() {
49
+		const tokens = [
50
+			new MDToken('Lorem', MDTokenType.Text),
51
+			new MDToken(' ', MDTokenType.Whitespace),
52
+			new MDToken('[ipsum]', MDTokenType.Label, 'ipsum'),
53
+			new MDToken(' ', MDTokenType.Whitespace),
54
+			new MDToken('(link.html)', MDTokenType.URL, 'link.html'),
55
+			new MDToken(' ', MDTokenType.Whitespace),
56
+			new MDToken('dolor', MDTokenType.Text),
57
+		];
58
+		const pattern = [
59
+			MDTokenType.Label,
60
+			MDTokenType.META_OptionalWhitespace,
61
+			MDTokenType.URL,
62
+		];
63
+		const result = MDToken.findFirstTokens(tokens, pattern);
64
+		const expected = {
65
+			tokens: [ tokens[2], tokens[3], tokens[4] ],
66
+			index: 2,
67
+		};
68
+		this.assertEqual(result, expected);
69
+	}
70
+
71
+	test_findPairedTokens() {
72
+		const tokens = [
73
+			new MDToken('Lorem', MDTokenType.Text),
74
+			new MDToken(' ', MDTokenType.Whitespace),
75
+			new MDToken('_', MDTokenType.Underscore),
76
+			new MDToken('ipsum', MDTokenType.Text),
77
+			new MDToken('_', MDTokenType.Underscore),
78
+			new MDToken(' ', MDTokenType.Whitespace),
79
+			new MDToken('dolor', MDTokenType.Text),
80
+			new MDToken('_', MDTokenType.Underscore),
81
+			new MDToken('sit', MDTokenType.Text),
82
+			new MDToken('_', MDTokenType.Underscore),
83
+		];
84
+		const pattern = [
85
+			MDTokenType.Underscore,
86
+		];
87
+		const result = MDToken.findPairedTokens(tokens, pattern, pattern);
88
+		const expected = {
89
+			startTokens: [ tokens[2] ],
90
+			contentTokens: [ tokens[3] ],
91
+			endTokens: [ tokens[4] ],
92
+			startIndex: 2,
93
+			contentIndex: 3,
94
+			endIndex: 4,
95
+			totalLength: 3,
96
+		}
97
+		this.assertEqual(result, expected);
98
+	}
99
+}

+ 84
- 0
jstest/UtilsTests.js ファイルの表示

@@ -0,0 +1,84 @@
1
+class UtilsTests extends BaseTest {
2
+	test_stripIndent() {
3
+		this.assertEqual(MDUtils.stripIndent(''), '');
4
+		this.assertEqual(MDUtils.stripIndent('  '), '');
5
+		this.assertEqual(MDUtils.stripIndent('foo'), 'foo');
6
+		this.assertEqual(MDUtils.stripIndent(' foo'), 'foo');
7
+		this.assertEqual(MDUtils.stripIndent('  foo'), 'foo');
8
+		this.assertEqual(MDUtils.stripIndent('   foo'), 'foo');
9
+		this.assertEqual(MDUtils.stripIndent('    foo'), 'foo');
10
+		this.assertEqual(MDUtils.stripIndent('     foo'), ' foo');
11
+		this.assertEqual(MDUtils.stripIndent('\tfoo'), 'foo');
12
+		this.assertEqual(MDUtils.stripIndent('\t\tfoo'), '\tfoo');
13
+		this.assertEqual(MDUtils.stripIndent('\t\tfoo', 2), 'foo');
14
+		this.assertEqual(MDUtils.stripIndent('      foo', 2), 'foo');
15
+	}
16
+
17
+	test_countIndents() {
18
+		this.assertEqual(MDUtils.countIndents(''), 0);
19
+		this.assertEqual(MDUtils.countIndents('  '), 1);
20
+		this.assertEqual(MDUtils.countIndents('    '), 1);
21
+		this.assertEqual(MDUtils.countIndents('foo'), 0);
22
+		this.assertEqual(MDUtils.countIndents('foo'), 0);
23
+		this.assertEqual(MDUtils.countIndents(' foo'), 1);
24
+		this.assertEqual(MDUtils.countIndents('  foo'), 1);
25
+		this.assertEqual(MDUtils.countIndents('   foo'), 1);
26
+		this.assertEqual(MDUtils.countIndents('    foo'), 1);
27
+		this.assertEqual(MDUtils.countIndents('     foo'), 2);
28
+		this.assertEqual(MDUtils.countIndents('\tfoo'), 1);
29
+		this.assertEqual(MDUtils.countIndents('\t\tfoo'), 2);
30
+
31
+		this.assertEqual(MDUtils.countIndents('', true), 0);
32
+		this.assertEqual(MDUtils.countIndents('  ', true), 0);
33
+		this.assertEqual(MDUtils.countIndents('    ', true), 1);
34
+		this.assertEqual(MDUtils.countIndents('foo', true), 0);
35
+		this.assertEqual(MDUtils.countIndents(' foo', true), 0);
36
+		this.assertEqual(MDUtils.countIndents('  foo', true), 0);
37
+		this.assertEqual(MDUtils.countIndents('   foo', true), 0);
38
+		this.assertEqual(MDUtils.countIndents('    foo', true), 1);
39
+		this.assertEqual(MDUtils.countIndents('     foo', true), 1);
40
+		this.assertEqual(MDUtils.countIndents('\tfoo', true), 1);
41
+		this.assertEqual(MDUtils.countIndents('\t\tfoo', true), 2);
42
+	}
43
+
44
+	test_tokenizeLabel() {
45
+		// Escapes are preserved
46
+		this.assertEqual(MDToken.tokenizeLabel('[foo] bar'), [ '[foo]', 'foo' ]);
47
+		this.assertEqual(MDToken.tokenizeLabel('[foo\\[] bar'), [ '[foo\\[]', 'foo\\[' ]);
48
+		this.assertEqual(MDToken.tokenizeLabel('[foo\\]] bar'), [ '[foo\\]]', 'foo\\]' ]);
49
+		this.assertEqual(MDToken.tokenizeLabel('[foo[]] bar'), [ '[foo[]]', 'foo[]' ]);
50
+		this.assertEqual(MDToken.tokenizeLabel('[foo\\(] bar'), [ '[foo\\(]', 'foo\\(' ]);
51
+		this.assertEqual(MDToken.tokenizeLabel('[foo\\)] bar'), [ '[foo\\)]', 'foo\\)' ]);
52
+		this.assertEqual(MDToken.tokenizeLabel('[foo()] bar'), [ '[foo()]', 'foo()' ]);
53
+
54
+		this.assertEqual(MDToken.tokenizeLabel('foo bar'), null);
55
+		this.assertEqual(MDToken.tokenizeLabel('[foo\\] bar'), null);
56
+		this.assertEqual(MDToken.tokenizeLabel('[foo bar'), null);
57
+		this.assertEqual(MDToken.tokenizeLabel('[foo[] bar'), null);
58
+	}
59
+
60
+	test_tokenizeURL() {
61
+		this.assertEqual(MDToken.tokenizeURL('(page.html) foo'), [ '(page.html)', 'page.html', null ]);
62
+		this.assertEqual(MDToken.tokenizeURL('(page.html "link title") foo'), [ '(page.html "link title")', 'page.html', 'link title' ]);
63
+		this.assertEqual(MDToken.tokenizeURL('(https://example.com/path/page.html?query=foo&bar=baz#fragment) foo'), [ '(https://example.com/path/page.html?query=foo&bar=baz#fragment)', 'https://example.com/path/page.html?query=foo&bar=baz#fragment', null ]);
64
+
65
+		this.assertEqual(MDToken.tokenizeURL('page.html foo'), null);
66
+		this.assertEqual(MDToken.tokenizeURL('(page.html foo'), null);
67
+		this.assertEqual(MDToken.tokenizeURL('page.html) foo'), null);
68
+		this.assertEqual(MDToken.tokenizeURL('(page.html "title) foo'), null);
69
+		this.assertEqual(MDToken.tokenizeURL('(page .html) foo'), null);
70
+		this.assertEqual(MDToken.tokenizeURL('(user@example.com) foo'), null);
71
+		this.assertEqual(MDToken.tokenizeURL('(user@example.com "title") foo'), null);
72
+	}
73
+
74
+	test_tokenizeEmail() {
75
+		this.assertEqual(MDToken.tokenizeEmail('(user@example.com)'), [ '(user@example.com)', 'user@example.com', null ]);
76
+		this.assertEqual(MDToken.tokenizeEmail('(user@example.com "link title")'), [ '(user@example.com "link title")', 'user@example.com', 'link title' ]);
77
+
78
+		this.assertEqual(MDToken.tokenizeEmail('(https://example.com) foo'), null);
79
+		this.assertEqual(MDToken.tokenizeEmail('(https://example.com "link title") foo'), null);
80
+		this.assertEqual(MDToken.tokenizeEmail('(user@example.com "link title) foo'), null);
81
+		this.assertEqual(MDToken.tokenizeEmail('(user@example.com foo'), null);
82
+		this.assertEqual(MDToken.tokenizeEmail('user@example.com) foo'), null);
83
+	}
84
+}

+ 123
- 0
jstest/basetest.js ファイルの表示

@@ -0,0 +1,123 @@
1
+class BaseTest {
2
+	numberDifferenceRatio = 0.000_001;
3
+
4
+	setUp() {}
5
+
6
+	tearDown() {}
7
+
8
+	/** @var {TestCaseRunner|null} */
9
+	currentRunner = null;
10
+
11
+	fail(failMessage=null) {
12
+		throw new FailureError(failMessage || 'failed');
13
+	}
14
+
15
+	assertTrue(test, failMessage=null) {
16
+		if (!test) this.fail(failMessage || `expected true, got ${test}`);
17
+	}
18
+
19
+	assertFalse(test, failMessage=null) {
20
+		if (test) this.fail(failMessage || `expected false, got ${test}`);
21
+	}
22
+
23
+	assertEqual(a, b, failMessage=null) {
24
+		if (BaseTest.#equal(a, b, this.numberDifferenceRatio)) return;
25
+		const aVal = (typeof a == 'string') ? `"${a}"` : `${a}`;
26
+		const bVal = (typeof b == 'string') ? `"${b}"` : `${b}`;
27
+		if (aVal.length > 20 || bVal.length > 20) {
28
+			this.fail(failMessage || `equality failed:\n${aVal}\n!=\n${bVal}`);
29
+		} else {
30
+			this.fail(failMessage || `equality failed: ${aVal} != ${bVal}`);
31
+		}
32
+	}
33
+
34
+	expectError(e=true) {
35
+		if (this.currentRunner) this.currentRunner.expectedError = e;
36
+	}
37
+
38
+	/**
39
+	 * @param {number} maxTime maximum time in seconds
40
+	 * @param {function} timedCode code to run and time
41
+	 */
42
+	profile(maxTime, timedCode) {
43
+		const startTime = performance.now();
44
+		const result = timedCode();
45
+		const endTime = performance.now();
46
+		const seconds = (endTime - startTime) / 1000.0;
47
+		if (seconds > maxTime) {
48
+			this.fail(`Expected <= ${maxTime}s execution time, actual ${seconds}s.`);
49
+		}
50
+		return result;
51
+	}
52
+
53
+	/**
54
+	 * @param {Array} a
55
+	 * @param {Array} b
56
+	 * @returns {boolean}
57
+	 */
58
+	static #equalArrays(a, b) {
59
+		if (a === b) return true;
60
+		if (!(a instanceof Array) || !(b instanceof Array)) return false;
61
+		if (a == null || b == null) return false;
62
+		if (a.length != b.length) return false;
63
+		for (var i = 0; i < a.length; i++) {
64
+			if (!this.#equal(a[i], b[i])) return false;
65
+		}
66
+		return true;
67
+	}
68
+
69
+	/**
70
+	 * @param {object} a
71
+	 * @param {object} b
72
+	 * @returns {boolean}
73
+	 */
74
+	static #equalObjects(a, b) {
75
+		if (a === b) return true;
76
+		if (!(a instanceof Object) || !(b instanceof Object)) return false;
77
+		if (a == null || b == null) return false;
78
+		if (a.equals !== undefined) {
79
+			return a.equals(b);
80
+		}
81
+		for (const key of Object.keys(a)) {
82
+			if (!this.#equal(a[key], b[key])) return false;
83
+		}
84
+		for (const key of Object.keys(b)) {
85
+			if (!this.#equal(a[key], b[key])) return false;
86
+		}
87
+		return true;
88
+	}
89
+
90
+	/**
91
+	 * @param {number} a
92
+	 * @param {number} b
93
+	 * @returns {boolean}
94
+	 */
95
+	static #equalNumbers(a, b, differenceRatio=0.000001) {
96
+		if (a === b) return true;
97
+		const largestAllowableDelta = Math.max(Math.abs(a), Math.abs(b)) * differenceRatio;
98
+		const delta = Math.abs(a - b);
99
+		return delta <= largestAllowableDelta;
100
+	}
101
+
102
+	/**
103
+	 * Tests for equality on lots of different kinds of values including objects
104
+	 * and arrays. Will use `.equals` on objects that implement it.
105
+	 *
106
+	 * @param {any} a
107
+	 * @param {any} b
108
+	 * @param {number} numberDifferenceRatio
109
+	 * @returns {boolean}
110
+	 */
111
+	static #equal(a, b, numberDifferenceRatio=0.000_001) {
112
+		if (a instanceof Array && b instanceof Array) {
113
+			return this.#equalArrays(a, b);
114
+		}
115
+		if (a instanceof Object && b instanceof Object) {
116
+			return this.#equalObjects(a, b);
117
+		}
118
+		if (typeof a === 'number' && typeof b === 'number') {
119
+			return this.#equalNumbers(a, b, numberDifferenceRatio);
120
+		}
121
+		return a === b;
122
+	}
123
+}

+ 57
- 0
jstest/spreadsheet/CellAddressRangeTests.js ファイルの表示

@@ -0,0 +1,57 @@
1
+class CellAddressRangeTests extends BaseTest {
2
+	test_iterator() {
3
+		const grid = new SpreadsheetGrid(3, 4);
4
+		let range = new CellAddressRange(new CellAddress(0, 0), new CellAddress(2, 3));
5
+		var visited = [];
6
+		var sanity = 100;
7
+		for (const address of range.cellsIn(grid)) {
8
+			visited.push(address.name);
9
+			if (sanity-- < 0) break;
10
+		}
11
+		const result = visited.join(',');
12
+		const expected = 'A1,A2,A3,A4,B1,B2,B3,B4,C1,C2,C3,C4';
13
+		this.assertEqual(result, expected);
14
+	}
15
+
16
+	test_iterator_column() {
17
+		const grid = new SpreadsheetGrid(3, 4);
18
+		let range = new CellAddressRange(new CellAddress(1, -1), new CellAddress(2, -1));
19
+		var visited = [];
20
+		var sanity = 100;
21
+		for (const address of range.cellsIn(grid)) {
22
+			visited.push(address.name);
23
+			if (sanity-- < 0) break;
24
+		}
25
+		const result = visited.join(',');
26
+		const expected = 'B1,B2,B3,B4,C1,C2,C3,C4';
27
+		this.assertEqual(result, expected);
28
+	}
29
+
30
+	test_iterator_beyondBounds() {
31
+		const grid = new SpreadsheetGrid(3, 4);
32
+		let range = new CellAddressRange(new CellAddress(0, 1), new CellAddress(9, 9));
33
+		var visited = [];
34
+		var sanity = 100;
35
+		for (const address of range.cellsIn(grid)) {
36
+			visited.push(address.name);
37
+			if (sanity-- < 0) break;
38
+		}
39
+		const result = visited.join(',');
40
+		const expected = 'A2,A3,A4,B2,B3,B4,C2,C3,C4';
41
+		this.assertEqual(result, expected);
42
+	}
43
+
44
+	test_iterator_outOfBounds() {
45
+		const grid = new SpreadsheetGrid(3, 4);
46
+		let range = new CellAddressRange(new CellAddress(5, 0), new CellAddress(5, 9));
47
+		var visited = [];
48
+		var sanity = 100;
49
+		for (const address of range.cellsIn(grid)) {
50
+			visited.push(address.name);
51
+			if (sanity-- < 0) break;
52
+		}
53
+		const result = visited.join(',');
54
+		const expected = '';
55
+		this.assertEqual(result, expected);
56
+	}
57
+}

+ 297
- 0
jstest/spreadsheet/CellValueTests.js ファイルの表示

@@ -0,0 +1,297 @@
1
+class CellValueTests extends BaseTest {
2
+	test_fromCellString_blank() {
3
+		var value;
4
+		value = CellValue.fromCellString('');
5
+		this.assertEqual(value.type, CellValue.TYPE_BLANK);
6
+		this.assertEqual(value.formattedValue, '');
7
+		this.assertEqual(value.value, null);
8
+		value = CellValue.fromCellString(' ');
9
+		this.assertEqual(value.type, CellValue.TYPE_BLANK);
10
+		this.assertEqual(value.formattedValue, '');
11
+		this.assertEqual(value.value, null);
12
+	}
13
+
14
+	test_fromCellString_number() {
15
+		var value;
16
+		value = CellValue.fromCellString('123');
17
+		this.assertEqual(value.type, CellValue.TYPE_NUMBER);
18
+		this.assertEqual(value.formattedValue, '123');
19
+		this.assertEqual(value.value, 123);
20
+		this.assertEqual(value.decimals, 0);
21
+		value = CellValue.fromCellString('-0');
22
+		this.assertEqual(value.type, CellValue.TYPE_NUMBER);
23
+		this.assertEqual(value.formattedValue, '-0');
24
+		this.assertEqual(value.value, 0);
25
+		this.assertEqual(value.decimals, 0);
26
+		value = CellValue.fromCellString('1,234');
27
+		this.assertEqual(value.type, CellValue.TYPE_NUMBER);
28
+		this.assertEqual(value.formattedValue, '1,234');
29
+		this.assertEqual(value.value, 1234);
30
+		this.assertEqual(value.decimals, 0);
31
+		value = CellValue.fromCellString('-1,234,567.89');
32
+		this.assertEqual(value.type, CellValue.TYPE_NUMBER);
33
+		this.assertEqual(value.formattedValue, '-1,234,567.89');
34
+		this.assertEqual(value.value, -1234567.89);
35
+		this.assertEqual(value.decimals, 2);
36
+	}
37
+
38
+	test_fromCellString_percent() {
39
+		var value;
40
+		value = CellValue.fromCellString('123%');
41
+		this.assertEqual(value.type, CellValue.TYPE_PERCENT);
42
+		this.assertEqual(value.formattedValue, '123%');
43
+		this.assertEqual(value.value, 1.23, 0.0001);
44
+		this.assertEqual(value.decimals, 0);
45
+		value = CellValue.fromCellString('-12.3%');
46
+		this.assertEqual(value.type, CellValue.TYPE_PERCENT);
47
+		this.assertEqual(value.formattedValue, '-12.3%');
48
+		this.assertEqual(value.value, -0.123, 0.0001);
49
+		this.assertEqual(value.decimals, 1);
50
+	}
51
+
52
+	test_fromCellString_currency() {
53
+		var value;
54
+		value = CellValue.fromCellString('$123');
55
+		this.assertEqual(value.type, CellValue.TYPE_CURRENCY);
56
+		this.assertEqual(value.formattedValue, '$123');
57
+		this.assertEqual(value.value, 123);
58
+		this.assertEqual(value.decimals, 0);
59
+		value = CellValue.fromCellString('-$12.34');
60
+		this.assertEqual(value.type, CellValue.TYPE_CURRENCY);
61
+		this.assertEqual(value.formattedValue, '-$12.34');
62
+		this.assertEqual(value.value, -12.34, 0.0001);
63
+		this.assertEqual(value.decimals, 2);
64
+	}
65
+
66
+	test_fromCellString_boolean() {
67
+		var value;
68
+		value = CellValue.fromCellString('true');
69
+		this.assertEqual(value.type, CellValue.TYPE_BOOLEAN);
70
+		this.assertEqual(value.formattedValue, 'TRUE');
71
+		this.assertEqual(value.value, true);
72
+		value = CellValue.fromCellString('false');
73
+		this.assertEqual(value.type, CellValue.TYPE_BOOLEAN);
74
+		this.assertEqual(value.formattedValue, 'FALSE');
75
+		this.assertEqual(value.value, false);
76
+	}
77
+
78
+	test_fromCellString_string() {
79
+		var value;
80
+		value = CellValue.fromCellString('some text');
81
+		this.assertEqual(value.type, CellValue.TYPE_STRING);
82
+		this.assertEqual(value.formattedValue, 'some text');
83
+		this.assertEqual(value.value, 'some text');
84
+		value = CellValue.fromCellString("'0123");
85
+		this.assertEqual(value.type, CellValue.TYPE_STRING);
86
+		this.assertEqual(value.formattedValue, '0123');
87
+		this.assertEqual(value.value, '0123');
88
+		value = CellValue.fromCellString("'=123");
89
+		this.assertEqual(value.type, CellValue.TYPE_STRING);
90
+		this.assertEqual(value.formattedValue, '=123');
91
+		this.assertEqual(value.value, '=123');
92
+	}
93
+
94
+	test_fromCellString_formula() {
95
+		var value;
96
+		value = CellValue.fromCellString('=A*B');
97
+		this.assertEqual(value.type, CellValue.TYPE_FORMULA);
98
+		this.assertEqual(value.formattedValue, '=A*B');
99
+		this.assertEqual(value.value, '=A*B');
100
+		value = CellValue.fromCellString('=MAX(A, 3)');
101
+		this.assertEqual(value.type, CellValue.TYPE_FORMULA);
102
+		this.assertEqual(value.formattedValue, '=MAX(A, 3)');
103
+		this.assertEqual(value.value, '=MAX(A, 3)');
104
+	}
105
+
106
+	test_fromValue_null() {
107
+		var value;
108
+		value = CellValue.fromValue(null);
109
+		this.assertEqual(value.type, CellValue.TYPE_BLANK);
110
+	}
111
+
112
+	test_fromValue_number() {
113
+		var value;
114
+		value = CellValue.fromValue(123);
115
+		this.assertEqual(value.type, CellValue.TYPE_NUMBER);
116
+		this.assertEqual(value.formattedValue, '123');
117
+		value = CellValue.fromValue(3.141592);
118
+		this.assertEqual(value.type, CellValue.TYPE_NUMBER);
119
+		this.assertEqual(value.formattedValue, '3.141592');
120
+		value = CellValue.fromValue(123456789);
121
+		this.assertEqual(value.type, CellValue.TYPE_NUMBER);
122
+		this.assertEqual(value.formattedValue, '123,456,789');
123
+	}
124
+
125
+	test_fromValue_boolean() {
126
+		var value;
127
+		value = CellValue.fromValue(true);
128
+		this.assertEqual(value.type, CellValue.TYPE_BOOLEAN);
129
+		this.assertEqual(value.formattedValue, 'TRUE');
130
+		value = CellValue.fromValue(false);
131
+		this.assertEqual(value.type, CellValue.TYPE_BOOLEAN);
132
+		this.assertEqual(value.formattedValue, 'FALSE');
133
+	}
134
+
135
+	test_fromValue_string() {
136
+		var value;
137
+		value = CellValue.fromValue('foo');
138
+		this.assertEqual(value.type, CellValue.TYPE_STRING);
139
+		this.assertEqual(value.formattedValue, 'foo');
140
+		value = CellValue.fromValue('123');
141
+		this.assertEqual(value.type, CellValue.TYPE_STRING);
142
+		this.assertEqual(value.formattedValue, '123');
143
+	}
144
+
145
+	test_fromValue_formula() {
146
+		var value;
147
+		value = CellValue.fromValue('=A*B');
148
+		this.assertEqual(value.type, CellValue.TYPE_FORMULA);
149
+		this.assertEqual(value.formattedValue, '=A*B');
150
+	}
151
+
152
+	test_operation_add() {
153
+		var a, b, result, expected;
154
+		a = CellValue.fromValue(3);
155
+		b = CellValue.fromValue(4);
156
+		result = a.add(b)
157
+		expected = new CellValue('7', 7, CellValue.TYPE_NUMBER, 0);
158
+		this.assertEqual(result, expected);
159
+
160
+		a = CellValue.fromCellString('100%');
161
+		b = CellValue.fromCellString('50%');
162
+		result = a.add(b);
163
+		expected = new CellValue('150%', 1.5, CellValue.TYPE_PERCENT, 0);
164
+		this.assertEqual(result, expected);
165
+
166
+		a = CellValue.fromCellString('$123');
167
+		b = CellValue.fromCellString('$321');
168
+		result = a.add(b);
169
+		expected = new CellValue('$444.00', 444, CellValue.TYPE_CURRENCY, 2);
170
+		this.assertEqual(result, expected);
171
+	}
172
+
173
+	test_operation_subtract() {
174
+		var a, b, result, expected;
175
+		a = CellValue.fromValue(9);
176
+		b = CellValue.fromValue(4);
177
+		result = a.subtract(b)
178
+		expected = new CellValue('5', 5, CellValue.TYPE_NUMBER, 0);
179
+		this.assertEqual(result, expected);
180
+
181
+		a = CellValue.fromCellString('100%');
182
+		b = CellValue.fromCellString('50%');
183
+		result = a.subtract(b);
184
+		expected = new CellValue('50%', 0.5, CellValue.TYPE_PERCENT, 0);
185
+		this.assertEqual(result, expected);
186
+
187
+		a = CellValue.fromCellString('$321');
188
+		b = CellValue.fromCellString('$123');
189
+		result = a.subtract(b);
190
+		expected = new CellValue('$198.00', 198, CellValue.TYPE_CURRENCY, 2);
191
+		this.assertEqual(result, expected);
192
+	}
193
+
194
+	test_operation_multiply() {
195
+		var a, b, result, expected;
196
+		a = CellValue.fromValue(3);
197
+		b = CellValue.fromValue(4);
198
+		result = a.multiply(b)
199
+		expected = new CellValue('12', 12, CellValue.TYPE_NUMBER, 0);
200
+		this.assertEqual(result, expected);
201
+
202
+		a = CellValue.fromCellString('150%');
203
+		b = CellValue.fromCellString('50%');
204
+		result = a.multiply(b);
205
+		expected = new CellValue('75%', 0.75, CellValue.TYPE_PERCENT, 0);
206
+		this.assertEqual(result, expected);
207
+
208
+		a = CellValue.fromCellString('$321');
209
+		b = CellValue.fromCellString('50%');
210
+		result = a.multiply(b);
211
+		expected = new CellValue('$160.50', 160.50, CellValue.TYPE_CURRENCY, 2);
212
+		this.assertEqual(result, expected);
213
+	}
214
+
215
+	test_operation_divide() {
216
+		var a, b, result, expected;
217
+		a = CellValue.fromValue(12);
218
+		b = CellValue.fromValue(4);
219
+		result = a.divide(b)
220
+		expected = new CellValue('3', 3, CellValue.TYPE_NUMBER, 0);
221
+		this.assertEqual(result, expected);
222
+
223
+		a = CellValue.fromCellString('150%');
224
+		b = CellValue.fromCellString('50%');
225
+		result = a.divide(b);
226
+		expected = new CellValue('300%', 3.0, CellValue.TYPE_PERCENT, 0);
227
+		this.assertEqual(result, expected);
228
+
229
+		a = CellValue.fromCellString('$321');
230
+		b = CellValue.fromCellString('200%');
231
+		result = a.divide(b);
232
+		expected = new CellValue('$160.50', 160.50, CellValue.TYPE_CURRENCY, 2);
233
+		this.assertEqual(result, expected);
234
+	}
235
+
236
+	test_operation_modulo() {
237
+		var a, b, result, expected;
238
+		a = CellValue.fromValue(7);
239
+		b = CellValue.fromValue(4);
240
+		result = a.modulo(b)
241
+		expected = new CellValue('3', 3, CellValue.TYPE_NUMBER, 0);
242
+		this.assertEqual(result, expected);
243
+
244
+		a = CellValue.fromCellString('175%');
245
+		b = CellValue.fromCellString('50%');
246
+		result = a.modulo(b);
247
+		expected = new CellValue('25%', 0.25, CellValue.TYPE_PERCENT, 0);
248
+		this.assertEqual(result, expected);
249
+
250
+		a = CellValue.fromCellString('$327');
251
+		b = CellValue.fromCellString('$20');
252
+		result = a.modulo(b);
253
+		expected = new CellValue('$7.00', 7.00, CellValue.TYPE_CURRENCY, 2);
254
+		this.assertEqual(result, expected);
255
+	}
256
+
257
+	test_operation_unaryNot() {
258
+		this.assertEqual(CellValue.fromValue(true).not(), CellValue.fromValue(false));
259
+		this.assertEqual(CellValue.fromValue(false).not(), CellValue.fromValue(true));
260
+	}
261
+
262
+	test_operation_comparators() {
263
+		const a = CellValue.fromValue(3);
264
+		const b = CellValue.fromValue(4);
265
+		const t = CellValue.fromValue(true);
266
+		const f = CellValue.fromValue(false);
267
+
268
+		this.assertEqual(a.lt(b), t);
269
+		this.assertEqual(a.lte(b), t);
270
+		this.assertEqual(a.gt(b), f);
271
+		this.assertEqual(a.gte(b), f);
272
+		this.assertEqual(a.eq(b), f);
273
+		this.assertEqual(a.neq(b), t);
274
+
275
+		this.assertEqual(b.lt(a), f);
276
+		this.assertEqual(b.lte(a), f);
277
+		this.assertEqual(b.gt(a), t);
278
+		this.assertEqual(b.gte(a), t);
279
+		this.assertEqual(b.eq(a), f);
280
+		this.assertEqual(b.neq(a), t);
281
+
282
+		this.assertEqual(a.lt(a), f);
283
+		this.assertEqual(a.lte(a), t);
284
+		this.assertEqual(a.gt(a), f);
285
+		this.assertEqual(a.gte(a), t);
286
+		this.assertEqual(a.eq(a), t);
287
+		this.assertEqual(a.neq(a), f);
288
+	}
289
+
290
+	test_operation_concatenate() {
291
+		const a = CellValue.fromValue('abc');
292
+		const b = CellValue.fromValue('xyz');
293
+		const result = a.concatenate(b);
294
+		const expected = CellValue.fromValue('abcxyz');
295
+		this.assertEqual(result, expected);
296
+	}
297
+}

+ 212
- 0
jstest/spreadsheet/ExpressionSetTests.js ファイルの表示

@@ -0,0 +1,212 @@
1
+class ExpressionSetTests extends BaseTest {
2
+	test_simpleMath() {
3
+		const grid = new SpreadsheetGrid(1, 1);
4
+		grid.cells[0][0].originalValue = CellValue.fromCellString('=7*3');
5
+		const expressionSet = new CellExpressionSet(grid);
6
+		expressionSet.calculateCells();
7
+		const expected = CellValue.fromValue(21);
8
+		this.assertEqual(grid.cells[0][0].outputValue, expected);
9
+	}
10
+
11
+	test_reference() {
12
+		const grid = new SpreadsheetGrid(3, 1);
13
+		grid.cells[0][0].originalValue = CellValue.fromValue(123);
14
+		grid.cells[1][0].originalValue = CellValue.fromValue(3);
15
+		grid.cells[2][0].originalValue = CellValue.fromCellString('=A1*B1');
16
+		const expressionSet = new CellExpressionSet(grid);
17
+		expressionSet.calculateCells();
18
+		const expected = CellValue.fromValue(369);
19
+		this.assertEqual(grid.cells[2][0].outputValue, expected);
20
+	}
21
+
22
+	test_infixPriority() {
23
+		const grid = new SpreadsheetGrid(1, 1);
24
+		grid.cells[0][0].originalValue = CellValue.fromCellString('=4*9/3+7-4');
25
+		const expressionSet = new CellExpressionSet(grid);
26
+		expressionSet.calculateCells();
27
+		const expected = CellValue.fromValue(15);
28
+		this.assertEqual(grid.cells[0][0].outputValue, expected);
29
+	}
30
+
31
+	test_filledFormula() {
32
+		const grid = new SpreadsheetGrid(3, 3);
33
+		grid.cells[0][0].originalValue = CellValue.fromValue(4);
34
+		grid.cells[1][0].originalValue = CellValue.fromValue(5);
35
+		grid.cells[2][0].originalValue = CellValue.fromCellString('=A1*B1 FILL');
36
+		grid.cells[0][1].originalValue = CellValue.fromValue(6);
37
+		grid.cells[1][1].originalValue = CellValue.fromValue(7);
38
+		grid.cells[0][2].originalValue = CellValue.fromValue(8);
39
+		grid.cells[1][2].originalValue = CellValue.fromValue(9);
40
+		const expressionSet = new CellExpressionSet(grid);
41
+		expressionSet.calculateCells();
42
+		this.assertEqual(grid.cells[2][0].outputValue, CellValue.fromValue(20));
43
+		this.assertEqual(grid.cells[2][1].outputValue, CellValue.fromValue(42));
44
+		this.assertEqual(grid.cells[2][2].outputValue, CellValue.fromValue(72));
45
+	}
46
+
47
+	test_dependencies() {
48
+		const grid = new SpreadsheetGrid(2, 4);
49
+		grid.cells[0][0].originalValue = CellValue.fromValue(1);
50
+		grid.cells[1][0].originalValue = CellValue.fromCellString('=A1+B2');
51
+		grid.cells[0][1].originalValue = CellValue.fromValue(2);
52
+		grid.cells[1][1].originalValue = CellValue.fromCellString('=A2+B3');
53
+		grid.cells[0][2].originalValue = CellValue.fromValue(3);
54
+		grid.cells[1][2].originalValue = CellValue.fromCellString('=A3+B4');
55
+		grid.cells[0][3].originalValue = CellValue.fromValue(4);
56
+		grid.cells[1][3].originalValue = CellValue.fromCellString('=A4');
57
+		const expressionSet = new CellExpressionSet(grid);
58
+		expressionSet.calculateCells();
59
+		this.assertEqual(grid.cells[1][0].outputValue, CellValue.fromValue(10));
60
+		this.assertEqual(grid.cells[1][1].outputValue, CellValue.fromValue(9));
61
+		this.assertEqual(grid.cells[1][2].outputValue, CellValue.fromValue(7));
62
+		this.assertEqual(grid.cells[1][3].outputValue, CellValue.fromValue(4));
63
+	}
64
+
65
+	_test_simple_formula(formula, expected) {
66
+		const grid = new SpreadsheetGrid(1, 1);
67
+		grid.cells[0][0].originalValue = CellValue.fromCellString(formula);
68
+		const expressionSet = new CellExpressionSet(grid);
69
+		expressionSet.calculateCells();
70
+		const result = grid.cells[0][0].outputValue;
71
+		const exp = (expected instanceof CellValue) ? expected : CellValue.fromValue(expected);
72
+		this.assertEqual(result.type, exp.type);
73
+		this.assertEqual(result.decimals, exp.decimals);
74
+		this.assertEqual(result.formattedValue, exp.formattedValue);
75
+		this.assertEqual(result.value, exp.value);
76
+	}
77
+
78
+	test_func_abs() {
79
+		this._test_simple_formula('=ABS(-3)', 3);
80
+		this._test_simple_formula('=ABS(4)', 4);
81
+	}
82
+
83
+	test_func_and() {
84
+		this._test_simple_formula('=AND(FALSE, FALSE)', false);
85
+		this._test_simple_formula('=AND(FALSE, TRUE)', false);
86
+		this._test_simple_formula('=AND(TRUE, FALSE)', false);
87
+		this._test_simple_formula('=AND(TRUE, TRUE)', true);
88
+		this._test_simple_formula('=AND(TRUE, TRUE, TRUE, TRUE)', true);
89
+		this._test_simple_formula('=AND(TRUE, TRUE, TRUE, FALSE)', false);
90
+	}
91
+
92
+	test_func_average() {
93
+		this._test_simple_formula('=AVERAGE(4, 6, 2, 4)', 4);
94
+		this._test_simple_formula('=AVERAGE(4, 6, 2, "foo", 4)', 4);
95
+	}
96
+
97
+	test_func_ceiling() {
98
+		this._test_simple_formula('=CEILING(3.1)', 4);
99
+		this._test_simple_formula('=CEILING(3)', 3);
100
+		this._test_simple_formula('=CEILING(-3.1)', -3);
101
+	}
102
+
103
+	test_func_exp() {
104
+		this._test_simple_formula('=EXP(1)', 2.718281828459045);
105
+	}
106
+
107
+	test_func_floor() {
108
+		this._test_simple_formula('=FLOOR(3.1)', 3);
109
+		this._test_simple_formula('=FLOOR(3)', 3);
110
+		this._test_simple_formula('=FLOOR(-3.1)', -4);
111
+	}
112
+
113
+	test_func_if() {
114
+		this._test_simple_formula('=IF(FALSE, 4, 6)', 6);
115
+		this._test_simple_formula('=IF(TRUE, 4, 6)', 4);
116
+	}
117
+
118
+	test_func_ifs() {
119
+		this._test_simple_formula('=IFS(TRUE, 1, FALSE, 2, FALSE, 3, FALSE, 4, 5)', 1);
120
+		this._test_simple_formula('=IFS(FALSE, 1, TRUE, 2, FALSE, 3, FALSE, 4, 5)', 2);
121
+		this._test_simple_formula('=IFS(FALSE, 1, FALSE, 2, TRUE, 3, FALSE, 4, 5)', 3);
122
+		this._test_simple_formula('=IFS(FALSE, 1, FALSE, 2, FALSE, 3, TRUE, 4, 5)', 4);
123
+		this._test_simple_formula('=IFS(FALSE, 1, FALSE, 2, FALSE, 3, FALSE, 4, 5)', 5);
124
+	}
125
+
126
+	test_func_ln() {
127
+		this._test_simple_formula('=LN(2.718281828459045)', 1);
128
+	}
129
+
130
+	test_func_log() {
131
+		this._test_simple_formula('=LOG(1000, 10)', 3);
132
+		this._test_simple_formula('=LOG(64, 2)', 6);
133
+	}
134
+
135
+	test_func_lower() {
136
+		this._test_simple_formula('=LOWER("MiXeD")', 'mixed');
137
+	}
138
+
139
+	test_func_max() {
140
+		this._test_simple_formula('=MAX(4, 8, 5, 2)', 8);
141
+	}
142
+
143
+	test_func_min() {
144
+		this._test_simple_formula('=MIN(4, 8, 5, 2)', 2);
145
+	}
146
+
147
+	test_func_mod() {
148
+		this._test_simple_formula('=MOD(37, 4)', 1);
149
+	}
150
+
151
+	test_func_not() {
152
+		this._test_simple_formula('=NOT(TRUE)', false);
153
+		this._test_simple_formula('=NOT(FALSE)', true);
154
+	}
155
+
156
+	test_func_or() {
157
+		this._test_simple_formula('=OR(FALSE, FALSE)', false);
158
+		this._test_simple_formula('=OR(FALSE, TRUE)', true);
159
+		this._test_simple_formula('=OR(TRUE, FALSE)', true);
160
+		this._test_simple_formula('=OR(TRUE, TRUE)', true);
161
+		this._test_simple_formula('=OR(FALSE, FALSE, FALSE, TRUE)', true);
162
+	}
163
+
164
+	test_func_power() {
165
+		this._test_simple_formula('=POWER(2, 3)', 8);
166
+	}
167
+
168
+	test_func_round() {
169
+		this._test_simple_formula('=ROUND(3.1)', 3);
170
+		this._test_simple_formula('=ROUND(3.5)', 4);
171
+		this._test_simple_formula('=ROUND(4)', 4);
172
+		this._test_simple_formula('=ROUND(-3.1)', -3);
173
+		this._test_simple_formula('=ROUND(-3.5)', -3);
174
+		this._test_simple_formula('=ROUND(-3.9)', -4);
175
+		this._test_simple_formula('=ROUND(3.1415926535, 1)', 3.1);
176
+		this._test_simple_formula('=ROUND(3.1415926535, 2)', 3.14);
177
+		this._test_simple_formula('=ROUND(31.415926535, -1)', 30);
178
+	}
179
+
180
+	test_func_sqrt() {
181
+		this._test_simple_formula('=SQRT(16)', 4);
182
+	}
183
+
184
+	test_func_substitute() {
185
+		this._test_simple_formula('=SUBSTITUTE("cat sat on the mat", "at", "ot")', 'cot sot on the mot');
186
+		this._test_simple_formula('=SUBSTITUTE("cAt saT on the mat", "at", "ot")', 'cot sot on the mot');
187
+		this._test_simple_formula('=SUBSTITUTE("c.*t s.*t on the m.*t", ".*t", "ot")', 'cot sot on the mot');
188
+	}
189
+
190
+	test_func_sum() {
191
+		this._test_simple_formula('=SUM(1, 2, 3, 4, 5)', 15);
192
+	}
193
+
194
+	test_func_upper() {
195
+		this._test_simple_formula('=UPPER("mIxEd")', 'MIXED');
196
+	}
197
+
198
+	test_func_xor() {
199
+		this._test_simple_formula('=XOR(FALSE, FALSE)', false);
200
+		this._test_simple_formula('=XOR(FALSE, TRUE)', true);
201
+		this._test_simple_formula('=XOR(TRUE, FALSE)', true);
202
+		this._test_simple_formula('=XOR(TRUE, TRUE)', false);
203
+		this._test_simple_formula('=XOR(FALSE, FALSE, TRUE)', true);
204
+		this._test_simple_formula('=XOR(TRUE, FALSE, TRUE)', false);
205
+	}
206
+
207
+	test_format() {
208
+		this._test_simple_formula('=2.718281828459045 ; number 3', new CellValue('2.718', 2.718281828459045, 'number', 3));
209
+		this._test_simple_formula('=2.718281828459045 ; percent 2', new CellValue('271.83%', 2.718281828459045, 'percent', 2));
210
+		this._test_simple_formula('=2.718281828459045 ; currency 2', new CellValue('$2.72', 2.718281828459045, 'currency', 2));
211
+	}
212
+}

+ 62
- 0
jstest/spreadsheet/SpreadsheetMarkdownIntegrationTests.js ファイルの表示

@@ -0,0 +1,62 @@
1
+class SpreadsheetMarkdownIntegrationTests extends BaseTest {
2
+	parser;
3
+
4
+	setUp() {
5
+		this.parser = new Markdown([ ...Markdown.allReaders, new MDSpreadsheetReader() ]);
6
+	}
7
+
8
+	md(markdown) {
9
+		return normalizeWhitespace(this.parser.toHTML(markdown));
10
+	}
11
+
12
+	test_integration() {
13
+		const markdown = '| A | B | C |\n| --- | --- | --- |\n| 3 | 4 | =A*B |';
14
+		const expected = '<table> <thead> ' +
15
+			'<tr> <th>A</th> <th>B</th> <th>C</th> </tr> ' +
16
+			'</thead> <tbody> <tr> ' +
17
+			'<td class="spreadsheet-type-number" data-numeric-value="3" data-string-value="3">3</td> ' +
18
+			'<td class="spreadsheet-type-number" data-numeric-value="4" data-string-value="4">4</td> ' +
19
+			'<td class="calculated spreadsheet-type-number" data-numeric-value="12" data-string-value="12">12</td> ' +
20
+			'</tr> </tbody> </table>';
21
+		const result = this.md(markdown);
22
+		this.assertEqual(result, expected);
23
+	}
24
+
25
+	test_bailOut() {
26
+		// If no formulas found table isn't modified
27
+		const markdown = '| A | B | C |\n| --- | --- | --- |\n| 3 | 4 | A*B |';
28
+		const expected = '<table> <thead> ' +
29
+			'<tr> <th>A</th> <th>B</th> <th>C</th> </tr> ' +
30
+			'</thead> <tbody> <tr> ' +
31
+			'<td>3</td> ' +
32
+			'<td>4</td> ' +
33
+			'<td>A*B</td> ' +
34
+			'</tr> </tbody> </table>';
35
+		const result = this.md(markdown);
36
+		this.assertEqual(result, expected);
37
+	}
38
+
39
+	test_observedError1() {
40
+		// Saw "Uncaught Error: columnIndex must be number, got string" from this
41
+		const markdown = '| Unit Price | Qty | Subtotal |\n| ---: | ---: | ---: |\n| $1.23 | 2 | =A*B FILL |\n| $4.99 | 6 | |\n| Total | | =SUM(C:C) |';
42
+		const expected = '<table> <thead> <tr> ' +
43
+			'<th style="text-align: right;">Unit Price</th> ' +
44
+			'<th style="text-align: right;">Qty</th> ' +
45
+			'<th>Subtotal</th> ' +
46
+			'</tr> </thead> <tbody> <tr> ' +
47
+			'<td class="spreadsheet-type-currency" style="text-align: right;" data-numeric-value="1.23" data-string-value="1.23">$1.23</td> ' +
48
+			'<td class="spreadsheet-type-number" style="text-align: right;" data-numeric-value="2" data-string-value="2">2</td> ' +
49
+			'<td class="calculated spreadsheet-type-currency" data-numeric-value="2.46" data-string-value="2.46">$2.46</td> ' +
50
+			'</tr> <tr> ' +
51
+			'<td class="spreadsheet-type-currency" style="text-align: right;" data-numeric-value="4.99" data-string-value="4.99">$4.99</td> ' +
52
+			'<td class="spreadsheet-type-number" style="text-align: right;" data-numeric-value="6" data-string-value="6">6</td> ' +
53
+			'<td class="calculated spreadsheet-type-currency" data-numeric-value="29.94" data-string-value="29.94">$29.94</td> ' +
54
+			'</tr> <tr> ' +
55
+			'<td class="spreadsheet-type-string" style="text-align: right;" data-string-value="Total">Total</td> ' +
56
+			'<td class="spreadsheet-type-blank" style="text-align: right;" data-numeric-value="0" data-string-value=""></td> ' +
57
+			'<td class="calculated spreadsheet-type-currency" data-numeric-value="32.4" data-string-value="32.4">$32.40</td> ' +
58
+			'</tr> </tbody> </table>';
59
+		const result = this.md(markdown);
60
+		this.assertEqual(result, expected);
61
+	}
62
+}

+ 3
- 0
minify.sh ファイルの表示

@@ -0,0 +1,3 @@
1
+#!/bin/sh
2
+terser js/markdown.js --compress --mangle --output js/markdown.min.js
3
+terser js/spreadsheet.js --compress --mangle --output js/spreadsheet.min.js

+ 3
- 3
php/markdown.php ファイルの表示

@@ -44,7 +44,7 @@ class MDUtils {
44 44
 	 * @param int $levels
45 45
 	 * @return string|string[]
46 46
 	 */
47
-	public static function stripIndent(string|array &$line, int $levels=1): string|array {
47
+	public static function stripIndent(string|array $line, int $levels=1): string|array {
48 48
 		$regex = "^(?: {1,4}|\\t){{$levels}}";
49 49
 		return is_array($line) ? array_map(fn(string $l): string => mb_ereg_replace($regex, '', $l), $line) : mb_ereg_replace($regex, '', $line);
50 50
 	}
@@ -56,9 +56,9 @@ class MDUtils {
56 56
 	 */
57 57
 	public static function countIndents(string $line, bool $fullIndentsOnly=false): int {
58 58
 		// normalize indents to tabs
59
-		$t = mb_ereg_replace($fullIndentsOnly ? "(?: {4}|\\t)" : "(?: {1,4}|\\t)", "\t", $line);
59
+		$t = mb_ereg_replace($fullIndentsOnly ? '(?: {4}|\\t)' : '(?: {1,4}|\\t)', "\t", $line);
60 60
 		// remove content after indent
61
-		$t = mb_ereg_replace("^(\\t*)(.*?)$", "\\1", $t);
61
+		$t = mb_ereg_replace('^(\\t*)(.*?)$', '\\1', $t);
62 62
 		// count tabs
63 63
 		return mb_strlen($t);
64 64
 	}

+ 92
- 0
phptest/UtilsTests.php ファイルの表示

@@ -0,0 +1,92 @@
1
+<?php
2
+declare(strict_types=1);
3
+
4
+use PHPUnit\Framework\TestCase;
5
+
6
+require_once __DIR__ . '/../php/markdown.php';
7
+
8
+final class UtilsTests extends TestCase {
9
+	public function test_stripIndent() {
10
+		$this->assertSame('', MDUtils::stripIndent(''));
11
+		$this->assertSame('', MDUtils::stripIndent('  '));
12
+		$this->assertSame('foo', MDUtils::stripIndent('foo'));
13
+		$this->assertSame('foo', MDUtils::stripIndent(' foo'));
14
+		$this->assertSame('foo', MDUtils::stripIndent('  foo'));
15
+		$this->assertSame('foo', MDUtils::stripIndent('   foo'));
16
+		$this->assertSame('foo', MDUtils::stripIndent('    foo'));
17
+		$this->assertSame(' foo', MDUtils::stripIndent('     foo'));
18
+		$this->assertSame('foo', MDUtils::stripIndent("\tfoo"));
19
+		$this->assertSame("\tfoo", MDUtils::stripIndent("\t\tfoo"));
20
+		$this->assertSame('foo', MDUtils::stripIndent("\t\tfoo", 2));
21
+		$this->assertSame('foo', MDUtils::stripIndent('      foo', 2));
22
+	}
23
+
24
+	public function test_countIndents() {
25
+		$this->assertSame(0, MDUtils::countIndents(''));
26
+		$this->assertSame(1, MDUtils::countIndents('  '));
27
+		$this->assertSame(1, MDUtils::countIndents('    '));
28
+		$this->assertSame(0, MDUtils::countIndents('foo'));
29
+		$this->assertSame(0, MDUtils::countIndents('foo'));
30
+		$this->assertSame(1, MDUtils::countIndents(' foo'));
31
+		$this->assertSame(1, MDUtils::countIndents('  foo'));
32
+		$this->assertSame(1, MDUtils::countIndents('   foo'));
33
+		$this->assertSame(1, MDUtils::countIndents('    foo'));
34
+		$this->assertSame(2, MDUtils::countIndents('     foo'));
35
+		$this->assertSame(1, MDUtils::countIndents("\tfoo"));
36
+		$this->assertSame(2, MDUtils::countIndents("\t\tfoo"));
37
+
38
+		$this->assertSame(0, MDUtils::countIndents('', true));
39
+		$this->assertSame(0, MDUtils::countIndents('  ', true));
40
+		$this->assertSame(1, MDUtils::countIndents('    ', true));
41
+		$this->assertSame(0, MDUtils::countIndents('foo', true));
42
+		$this->assertSame(0, MDUtils::countIndents(' foo', true));
43
+		$this->assertSame(0, MDUtils::countIndents('  foo', true));
44
+		$this->assertSame(0, MDUtils::countIndents('   foo', true));
45
+		$this->assertSame(1, MDUtils::countIndents('    foo', true));
46
+		$this->assertSame(1, MDUtils::countIndents('     foo', true));
47
+		$this->assertSame(1, MDUtils::countIndents("\tfoo", true));
48
+		$this->assertSame(2, MDUtils::countIndents("\t\tfoo", true));
49
+	}
50
+
51
+	public function test_tokenizeLabel() {
52
+		// Escapes are preserved
53
+		$this->assertSame([ '[foo]', 'foo' ], MDToken::tokenizeLabel('[foo] bar'));
54
+		$this->assertSame([ '[foo\\[]', 'foo\\[' ], MDToken::tokenizeLabel('[foo\\[] bar'));
55
+		$this->assertSame([ '[foo\\]]', 'foo\\]' ], MDToken::tokenizeLabel('[foo\\]] bar'));
56
+		$this->assertSame([ '[foo[]]', 'foo[]' ], MDToken::tokenizeLabel('[foo[]] bar'));
57
+		$this->assertSame([ '[foo\\(]', 'foo\\(' ], MDToken::tokenizeLabel('[foo\\(] bar'));
58
+		$this->assertSame([ '[foo\\)]', 'foo\\)' ], MDToken::tokenizeLabel('[foo\\)] bar'));
59
+		$this->assertSame([ '[foo()]', 'foo()' ], MDToken::tokenizeLabel('[foo()] bar'));
60
+
61
+		$this->assertSame(null, MDToken::tokenizeLabel('foo bar'));
62
+		$this->assertSame(null, MDToken::tokenizeLabel('[foo\\] bar'));
63
+		$this->assertSame(null, MDToken::tokenizeLabel('[foo bar'));
64
+		$this->assertSame(null, MDToken::tokenizeLabel('[foo[] bar'));
65
+	}
66
+
67
+	public function test_tokenizeURL() {
68
+		$this->assertSame([ '(page.html)', 'page.html', null ], MDToken::tokenizeURL('(page.html) foo'));
69
+		$this->assertSame([ '(page.html "link title")', 'page.html', 'link title' ], MDToken::tokenizeURL('(page.html "link title") foo'));
70
+		$this->assertSame([ '(https://example.com/path/page.html?query=foo&bar=baz#fragment)', 'https://example.com/path/page.html?query=foo&bar=baz#fragment', null ], MDToken::tokenizeURL('(https://example.com/path/page.html?query=foo&bar=baz#fragment) foo'));
71
+
72
+		$this->assertSame(null, MDToken::tokenizeURL('page.html foo'));
73
+		$this->assertSame(null, MDToken::tokenizeURL('(page.html foo'));
74
+		$this->assertSame(null, MDToken::tokenizeURL('page.html) foo'));
75
+		$this->assertSame(null, MDToken::tokenizeURL('(page.html "title) foo'));
76
+		$this->assertSame(null, MDToken::tokenizeURL('(page .html) foo'));
77
+		$this->assertSame(null, MDToken::tokenizeURL('(user@example.com) foo'));
78
+		$this->assertSame(null, MDToken::tokenizeURL('(user@example.com "title") foo'));
79
+	}
80
+
81
+	public function test_tokenizeEmail() {
82
+		$this->assertSame([ '(user@example.com)', 'user@example.com', null ], MDToken::tokenizeEmail('(user@example.com)'));
83
+		$this->assertSame([ '(user@example.com "link title")', 'user@example.com', 'link title' ], MDToken::tokenizeEmail('(user@example.com "link title")'));
84
+
85
+		$this->assertSame(null, MDToken::tokenizeEmail('(https://example.com) foo'));
86
+		$this->assertSame(null, MDToken::tokenizeEmail('(https://example.com "link title") foo'));
87
+		$this->assertSame(null, MDToken::tokenizeEmail('(user@example.com "link title) foo'));
88
+		$this->assertSame(null, MDToken::tokenizeEmail('(user@example.com foo'));
89
+		$this->assertSame(null, MDToken::tokenizeEmail('user@example.com) foo'));
90
+	}
91
+}
92
+?>

+ 2
- 0
runphptests.sh ファイルの表示

@@ -0,0 +1,2 @@
1
+#!/bin/sh
2
+php lib/phpunit.phar phptest/UtilsTests.php

+ 9
- 1377
testjs.html
ファイル差分が大きすぎるため省略します
ファイルの表示


読み込み中…
キャンセル
保存