extractor.createJsParser)([
285 | () => {},
286 | 42
287 | ]);
288 | }).toThrowError(`Invalid extractor function provided. '42' is not a function`);
289 | });
290 | });
291 | });
292 |
--------------------------------------------------------------------------------
/tests/e2e/fixtures/html/embeddedJs.expected.pot:
--------------------------------------------------------------------------------
1 | msgid ""
2 | msgstr ""
3 | "Content-Type: text/plain; charset=UTF-8\n"
4 |
5 | #: tests/e2e/fixtures/html/embeddedJs.html:10
6 | msgid "Hello World!"
7 | msgstr ""
8 |
--------------------------------------------------------------------------------
/tests/e2e/fixtures/html/embeddedJs.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/tests/e2e/fixtures/html/example.expected.pot:
--------------------------------------------------------------------------------
1 | msgid ""
2 | msgstr ""
3 | "Content-Type: text/plain; charset=UTF-8\n"
4 |
5 | #: tests/e2e/fixtures/html/header.html:2
6 | msgid "Logo"
7 | msgstr ""
8 |
9 | #: tests/e2e/fixtures/html/header.html:4
10 | msgctxt "Menu"
11 | msgid "{{n}} notification"
12 | msgid_plural "{{n}} notifications"
13 | msgstr[0] ""
14 | msgstr[1] ""
15 |
16 | #. Comment
17 | #: tests/e2e/fixtures/html/header.html:6
18 | msgctxt "Menu"
19 | msgid "Logout"
20 | msgstr ""
21 |
22 | #: tests/e2e/fixtures/html/header.html:5
23 | msgctxt "Menu"
24 | msgid "Settings"
25 | msgstr ""
26 |
--------------------------------------------------------------------------------
/tests/e2e/fixtures/html/header.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
--------------------------------------------------------------------------------
/tests/e2e/fixtures/html/linenumberStart.expected.pot:
--------------------------------------------------------------------------------
1 | msgid ""
2 | msgstr ""
3 | "Content-Type: text/plain; charset=UTF-8\n"
4 |
5 | #: tests/e2e/fixtures/html/linenumberStart.html:23
6 | msgid "%{n} apple"
7 | msgid_plural "%{n} apples"
8 | msgstr[0] ""
9 | msgstr[1] ""
10 |
11 | #: tests/e2e/fixtures/html/linenumberStart.html:22
12 | msgid "msg 1"
13 | msgstr ""
14 |
15 | #: tests/e2e/fixtures/html/linenumberStart.html:24
16 | msgid "msg 2"
17 | msgstr ""
18 |
19 | #: tests/e2e/fixtures/html/linenumberStart.html:19
20 | msgctxt "title"
21 | msgid "%{n} apple"
22 | msgid_plural "%{n} apples"
23 | msgstr[0] ""
24 | msgstr[1] ""
25 |
--------------------------------------------------------------------------------
/tests/e2e/fixtures/html/linenumberStart.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
11 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/tests/e2e/fixtures/html/template.expected.pot:
--------------------------------------------------------------------------------
1 | msgid ""
2 | msgstr ""
3 | "Content-Type: text/plain; charset=UTF-8\n"
4 |
5 | #: tests/e2e/fixtures/html/template.html:2
6 | msgid "Logo"
7 | msgstr ""
8 |
9 | #: tests/e2e/fixtures/html/template.html:4
10 | msgctxt "Menu"
11 | msgid "{{n}} notification"
12 | msgid_plural "{{n}} notifications"
13 | msgstr[0] ""
14 | msgstr[1] ""
15 |
16 | #: tests/e2e/fixtures/html/template.html:6
17 | msgctxt "Menu"
18 | msgid "Logout"
19 | msgstr ""
20 |
21 | #: tests/e2e/fixtures/html/template.html:5
22 | msgctxt "Menu"
23 | msgid "Settings"
24 | msgstr ""
25 |
--------------------------------------------------------------------------------
/tests/e2e/fixtures/html/template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
--------------------------------------------------------------------------------
/tests/e2e/fixtures/js/example.expected.pot:
--------------------------------------------------------------------------------
1 | msgid ""
2 | msgstr ""
3 | "Content-Type: text/plain; charset=UTF-8\n"
4 |
5 | #: tests/e2e/fixtures/js/view.jsx:22
6 | msgid "One new message"
7 | msgid_plural "{{n}} new messages"
8 | msgstr[0] ""
9 | msgstr[1] ""
10 |
11 | #. Comment
12 | #: tests/e2e/fixtures/js/view.jsx:14
13 | msgid "Refresh"
14 | msgstr ""
15 |
16 | #: tests/e2e/fixtures/js/hello.jsx:5
17 | msgctxt "title"
18 | msgid "Hello World!"
19 | msgstr ""
20 |
--------------------------------------------------------------------------------
/tests/e2e/fixtures/js/hello.jsx:
--------------------------------------------------------------------------------
1 | export class HelloWorld extends React.Component {
2 | render() {
3 | return (
4 |
5 |
{ t('Hello World!', 'title') }
6 |
7 | );
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/tests/e2e/fixtures/js/multiline.expected.pot:
--------------------------------------------------------------------------------
1 | msgid ""
2 | msgstr ""
3 | "Content-Type: text/plain; charset=UTF-8\n"
4 |
5 | #: tests/e2e/fixtures/js/multiline.js:1
6 | msgid ""
7 | "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam\n"
8 | " nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam\n"
9 | " erat, sed diam voluptua."
10 | msgstr ""
11 |
12 | #: tests/e2e/fixtures/js/multiline.js:7
13 | msgid ""
14 | "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam\n"
15 | "nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam\n"
16 | "erat, sed diam voluptua."
17 | msgstr ""
18 |
--------------------------------------------------------------------------------
/tests/e2e/fixtures/js/multiline.js:
--------------------------------------------------------------------------------
1 | translate(
2 | `Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam
3 | nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam
4 | erat, sed diam voluptua.`
5 | );
6 |
7 | translate_trim(
8 | `Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam
9 | nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam
10 | erat, sed diam voluptua.`
11 | );
12 |
--------------------------------------------------------------------------------
/tests/e2e/fixtures/js/view.jsx:
--------------------------------------------------------------------------------
1 | import { HelloWorld } from 'tests/e2e/fixtures/js/hello.jsx';
2 |
3 | export class View extends React.Component {
4 | constructor(props) {
5 | super(props);
6 | this.translations = props.translationService;
7 | }
8 |
9 | render() {
10 | return (
11 |
12 |
13 |
16 |
17 | );
18 | }
19 |
20 | refresh() {
21 | let count = Math.round(Math.random() * 100);
22 | alert(this.translations.plural(count, 'One new message', '{{n}} new messages'));
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/tests/e2e/html.test.ts:
--------------------------------------------------------------------------------
1 | import * as fs from 'fs';
2 | import { GettextExtractor, HtmlExtractors, JsExtractors } from '../../dist';
3 |
4 | describe('HTML E2E', () => {
5 |
6 | test('example', () => {
7 | let extractor = new GettextExtractor();
8 |
9 | extractor
10 | .createHtmlParser([
11 | HtmlExtractors.elementContent('[translate]', {
12 | attributes: {
13 | textPlural: 'translate-plural',
14 | context: 'translation-context',
15 | comment: 'translation-comment'
16 | }
17 | }),
18 | HtmlExtractors.elementAttribute('[translate-alt]', 'alt')
19 | ])
20 | .parseFile('tests/e2e/fixtures/html/header.html');
21 |
22 | expect(extractor.getPotString()).toBe(fs.readFileSync(__dirname + '/fixtures/html/example.expected.pot').toString());
23 | });
24 |
25 | test('template element', () => {
26 | let extractor = new GettextExtractor();
27 |
28 | extractor
29 | .createHtmlParser([
30 | HtmlExtractors.elementContent('[translate]', {
31 | attributes: {
32 | textPlural: 'translate-plural',
33 | context: 'translation-context'
34 | }
35 | }),
36 | HtmlExtractors.elementAttribute('[translate-alt]', 'alt')
37 | ])
38 | .parseFile('tests/e2e/fixtures/html/template.html');
39 |
40 | expect(extractor.getPotString()).toBe(fs.readFileSync(__dirname + '/fixtures/html/template.expected.pot').toString());
41 | });
42 |
43 | test('embedded js', () => {
44 | let extractor = new GettextExtractor();
45 |
46 | let jsParser = extractor.createJsParser([
47 | JsExtractors.callExpression('translations.getText', {
48 | arguments: {
49 | text: 0
50 | }
51 | })
52 | ]);
53 |
54 | extractor
55 | .createHtmlParser([
56 | HtmlExtractors.embeddedJs('script[type=text/javascript]', jsParser)
57 | ])
58 | .parseFile('tests/e2e/fixtures/html/embeddedJs.html');
59 |
60 | expect(extractor.getPotString()).toBe(fs.readFileSync(__dirname + '/fixtures/html/embeddedJs.expected.pot').toString());
61 | });
62 |
63 | test('line number start 11', () => {
64 | const extractor = new GettextExtractor();
65 | const jsParser = extractor.createJsParser([
66 | JsExtractors.callExpression('__', { arguments: { text: 0, } }),
67 | JsExtractors.callExpression('_n', { arguments: { text: 0, textPlural: 1 } }),
68 | JsExtractors.callExpression('_xn', { arguments: { context: 0, text: 1, textPlural: 2 } }),
69 | ]);
70 | const htmlParser = extractor.createHtmlParser([
71 | HtmlExtractors.embeddedAttributeJs(/:title/, jsParser),
72 | HtmlExtractors.embeddedJs('script', jsParser),
73 | ]);
74 | htmlParser.parseFile('tests/e2e/fixtures/html/linenumberStart.html', { lineNumberStart: 11 });
75 | expect(extractor.getPotString())
76 | .toBe(fs.readFileSync(__dirname + '/fixtures/html/linenumberStart.expected.pot').toString())
77 | });
78 |
79 | });
80 |
--------------------------------------------------------------------------------
/tests/e2e/html/elementAttribute.test.ts:
--------------------------------------------------------------------------------
1 | import { IHtmlExtractorFunction } from '../../../dist/html/parser';
2 | import { IMessage } from '../../../dist/builder';
3 | import { GettextExtractor, HtmlExtractors } from '../../../dist';
4 | import { trimIndent } from '../../indent';
5 |
6 | describe('standard', () => {
7 |
8 | test('just text', assertMessages(
9 | HtmlExtractors.elementAttribute('translate', 'text', {
10 | attributes: {
11 | context: 'context',
12 | textPlural: 'plural'
13 | }
14 | }),
15 | ``,
16 | {
17 | text: 'Foo'
18 | }
19 | ));
20 |
21 | test('with context', assertMessages(
22 | HtmlExtractors.elementAttribute('translate', 'text', {
23 | attributes: {
24 | context: 'context',
25 | textPlural: 'plural'
26 | }
27 | }),
28 | ``,
29 | {
30 | text: 'Foo',
31 | context: 'Context'
32 | }
33 | ));
34 |
35 | test('plural', assertMessages(
36 | HtmlExtractors.elementAttribute('translate', 'text', {
37 | attributes: {
38 | context: 'context',
39 | textPlural: 'plural'
40 | }
41 | }),
42 | ``,
43 | {
44 | text: 'Foo',
45 | textPlural: 'Foos'
46 | }
47 | ));
48 |
49 | test('plural with context', assertMessages(
50 | HtmlExtractors.elementAttribute('translate', 'text', {
51 | attributes: {
52 | context: 'context',
53 | textPlural: 'plural'
54 | }
55 | }),
56 | ``,
57 | {
58 | text: 'Foo',
59 | textPlural: 'Foos',
60 | context: 'Context'
61 | }
62 | ));
63 |
64 | test('missing text', assertMessages(
65 | HtmlExtractors.elementAttribute('translate', 'text', {
66 | attributes: {
67 | context: 'context',
68 | textPlural: 'plural'
69 | }
70 | }),
71 | ``
72 | ));
73 | });
74 |
75 | describe('just text', () => {
76 |
77 | test('with context', assertMessages(
78 | HtmlExtractors.elementAttribute('translate', 'text'),
79 | ``,
80 | {
81 | text: 'Foo'
82 | }
83 | ));
84 |
85 | test('plural', assertMessages(
86 | HtmlExtractors.elementAttribute('translate', 'text'),
87 | ``,
88 | {
89 | text: 'Foo'
90 | }
91 | ));
92 |
93 | test('plural with context', assertMessages(
94 | HtmlExtractors.elementAttribute('translate', 'text'),
95 | ``,
96 | {
97 | text: 'Foo'
98 | }
99 | ));
100 | });
101 |
102 | describe('comment', () => {
103 |
104 | test('just text', assertMessages(
105 | HtmlExtractors.elementAttribute('translate', 'text', {
106 | attributes: {
107 | comment: 'comment'
108 | }
109 | }),
110 | ``,
111 | {
112 | text: 'Foo'
113 | }
114 | ));
115 |
116 | test('with comment', assertMessages(
117 | HtmlExtractors.elementAttribute('translate', 'text', {
118 | attributes: {
119 | comment: 'comment'
120 | }
121 | }),
122 | ``,
123 | {
124 | text: 'Foo',
125 | comments: [
126 | 'Foo Bar'
127 | ]
128 | }
129 | ));
130 |
131 | test('empty comment', assertMessages(
132 | HtmlExtractors.elementAttribute('translate', 'text', {
133 | attributes: {
134 | comment: 'comment'
135 | }
136 | }),
137 | ``,
138 | {
139 | text: 'Foo'
140 | }
141 | ));
142 | });
143 |
144 | describe('argument validation', () => {
145 |
146 | test('selector: (none)', () => {
147 | expect(() => {
148 | (HtmlExtractors.elementAttribute)();
149 | }).toThrowError(`Missing argument 'selector'`);
150 | });
151 |
152 | test('selector: null', () => {
153 | expect(() => {
154 | (HtmlExtractors.elementAttribute)(null);
155 | }).toThrowError(`Argument 'selector' must be a non-empty string`);
156 | });
157 |
158 | test('selector: wrong type', () => {
159 | expect(() => {
160 | (HtmlExtractors.elementAttribute)(42);
161 | }).toThrowError(`Argument 'selector' must be a non-empty string`);
162 | });
163 |
164 | test('textAttribute: (none)', () => {
165 | expect(() => {
166 | (HtmlExtractors.elementAttribute)('[translate]');
167 | }).toThrowError(`Missing argument 'textAttribute'`);
168 | });
169 |
170 | test('textAttribute: null', () => {
171 | expect(() => {
172 | (HtmlExtractors.elementAttribute)('[translate]', null);
173 | }).toThrowError(`Argument 'textAttribute' must be a non-empty string`);
174 | });
175 |
176 | test('textAttribute: wrong type', () => {
177 | expect(() => {
178 | (HtmlExtractors.elementAttribute)('[translate]', 42);
179 | }).toThrowError(`Argument 'textAttribute' must be a non-empty string`);
180 | });
181 |
182 | test('options: wrong type', () => {
183 | expect(() => {
184 | (HtmlExtractors.elementAttribute)('[translate]', 'translate', 'foo');
185 | }).toThrowError(`Argument 'options' must be an object`);
186 | });
187 |
188 | test('options.attributes: wrong type', () => {
189 | expect(() => {
190 | (HtmlExtractors.elementAttribute)('[translate]', 'translate', {
191 | attributes: 'foo'
192 | });
193 | }).toThrowError(`Property 'options.attributes' must be an object`);
194 | });
195 |
196 | test('options.attributes.textPlural: wrong type', () => {
197 | expect(() => {
198 | (HtmlExtractors.elementAttribute)('[translate]', 'translate', {
199 | attributes: {
200 | textPlural: 42
201 | }
202 | });
203 | }).toThrowError(`Property 'options.attributes.textPlural' must be a string`);
204 | });
205 |
206 | test('options.attributes.context: wrong type', () => {
207 | expect(() => {
208 | (HtmlExtractors.elementAttribute)('[translate]', 'translate', {
209 | attributes: {
210 | context: 42
211 | }
212 | });
213 | }).toThrowError(`Property 'options.attributes.context' must be a string`);
214 | });
215 |
216 | test('options.attributes.comment: wrong type', () => {
217 | expect(() => {
218 | (HtmlExtractors.elementAttribute)('[translate]', 'translate', {
219 | attributes: {
220 | comment: 42
221 | }
222 | });
223 | }).toThrowError(`Property 'options.attributes.comment' must be a string`);
224 | });
225 |
226 | test('options.content: wrong type', () => {
227 | expect(() => {
228 | (HtmlExtractors.elementAttribute)('[translate]', 'translate', {
229 | content: 'foo'
230 | });
231 | }).toThrowError(`Property 'options.content' must be an object`);
232 | });
233 |
234 | test('options.content.trimWhiteSpace: wrong type', () => {
235 | expect(() => {
236 | (HtmlExtractors.elementAttribute)('[translate]', 'translate', {
237 | content: {
238 | trimWhiteSpace: 'foo'
239 | }
240 | });
241 | }).toThrowError(`Property 'options.content.trimWhiteSpace' must be a boolean`);
242 | });
243 |
244 | test('options.content.preserveIndentation: wrong type', () => {
245 | expect(() => {
246 | (HtmlExtractors.elementAttribute)('[translate]', 'translate', {
247 | content: {
248 | preserveIndentation: 'foo'
249 | }
250 | });
251 | }).toThrowError(`Property 'options.content.preserveIndentation' must be a boolean`);
252 | });
253 |
254 | test('options.content.replaceNewLines: wrong type', () => {
255 | expect(() => {
256 | (HtmlExtractors.elementAttribute)('[translate]', 'translate', {
257 | content: {
258 | replaceNewLines: 42
259 | }
260 | });
261 | }).toThrowError(`Property 'options.content.replaceNewLines' must be false or a string`);
262 | });
263 |
264 | test('options.content.replaceNewLines: true', () => {
265 | expect(() => {
266 | (HtmlExtractors.elementAttribute)('[translate]', 'translate', {
267 | content: {
268 | replaceNewLines: true
269 | }
270 | });
271 | }).toThrowError(`Property 'options.content.replaceNewLines' must be false or a string`);
272 | });
273 | });
274 |
275 | function assertMessages(extractorFunction: IHtmlExtractorFunction, source: string, ...expected: Partial[]): () => void {
276 | return () => {
277 | let extractor = new GettextExtractor();
278 |
279 | extractor.createHtmlParser([extractorFunction]).parseString(source);
280 |
281 | expect(extractor.getMessages()).toStrictEqual(
282 | expected.map(message => ({
283 | textPlural: null,
284 | context: null,
285 | comments: [],
286 | references: [],
287 | ...message
288 | }))
289 | );
290 | };
291 | }
292 |
--------------------------------------------------------------------------------
/tests/e2e/html/elementContent.test.ts:
--------------------------------------------------------------------------------
1 | import { GettextExtractor, HtmlExtractors } from '../../../dist';
2 | import { IHtmlExtractorFunction } from '../../../dist/html/parser';
3 | import { IMessage } from '../../../dist/builder';
4 | import { trimIndent } from '../../indent';
5 |
6 | describe('standard', () => {
7 |
8 | test('just text', assertMessages(
9 | HtmlExtractors.elementContent('translate', {
10 | attributes: {
11 | context: 'context',
12 | textPlural: 'plural'
13 | }
14 | }),
15 | `Foo`,
16 | {
17 | text: 'Foo'
18 | }
19 | ));
20 |
21 | test('with context', assertMessages(
22 | HtmlExtractors.elementContent('translate', {
23 | attributes: {
24 | context: 'context',
25 | textPlural: 'plural'
26 | }
27 | }),
28 | `Foo`,
29 | {
30 | text: 'Foo',
31 | context: 'Context'
32 | }
33 | ));
34 |
35 | test('plural', assertMessages(
36 | HtmlExtractors.elementContent('translate', {
37 | attributes: {
38 | context: 'context',
39 | textPlural: 'plural'
40 | }
41 | }),
42 | `Foo`,
43 | {
44 | text: 'Foo',
45 | textPlural: 'Foos'
46 | }
47 | ));
48 |
49 | test('plural with context', assertMessages(
50 | HtmlExtractors.elementContent('translate', {
51 | attributes: {
52 | context: 'context',
53 | textPlural: 'plural'
54 | }
55 | }),
56 | `Foo`,
57 | {
58 | text: 'Foo',
59 | textPlural: 'Foos',
60 | context: 'Context'
61 | }
62 | ));
63 | });
64 |
65 | describe('just text', () => {
66 |
67 | test('with context', assertMessages(
68 | HtmlExtractors.elementContent('translate'),
69 | `Foo`,
70 | {
71 | text: 'Foo'
72 | }
73 | ));
74 |
75 | test('plural', assertMessages(
76 | HtmlExtractors.elementContent('translate'),
77 | `Foo`,
78 | {
79 | text: 'Foo'
80 | }
81 | ));
82 |
83 | test('plural with context', assertMessages(
84 | HtmlExtractors.elementContent('translate'),
85 | `Foo`,
86 | {
87 | text: 'Foo'
88 | }
89 | ));
90 | });
91 |
92 | describe('comment', () => {
93 |
94 | test('just text', assertMessages(
95 | HtmlExtractors.elementContent('translate', {
96 | attributes: {
97 | comment: 'comment'
98 | }
99 | }),
100 | `Foo`,
101 | {
102 | text: 'Foo'
103 | }
104 | ));
105 |
106 | test('with comment', assertMessages(
107 | HtmlExtractors.elementContent('translate', {
108 | attributes: {
109 | comment: 'comment'
110 | }
111 | }),
112 | `Foo`,
113 | {
114 | text: 'Foo',
115 | comments: [
116 | 'Foo Bar'
117 | ]
118 | }
119 | ));
120 |
121 | test('empty comment', assertMessages(
122 | HtmlExtractors.elementContent('translate', {
123 | attributes: {
124 | comment: 'comment'
125 | }
126 | }),
127 | `Foo`,
128 | {
129 | text: 'Foo'
130 | }
131 | ));
132 | });
133 |
134 | describe('argument validation', () => {
135 |
136 | test('selector: (none)', () => {
137 | expect(() => {
138 | (HtmlExtractors.elementContent)();
139 | }).toThrowError(`Missing argument 'selector'`);
140 | });
141 |
142 | test('selector: null', () => {
143 | expect(() => {
144 | (HtmlExtractors.elementContent)(null);
145 | }).toThrowError(`Argument 'selector' must be a non-empty string`);
146 | });
147 |
148 | test('selector: wrong type', () => {
149 | expect(() => {
150 | (HtmlExtractors.elementContent)(42);
151 | }).toThrowError(`Argument 'selector' must be a non-empty string`);
152 | });
153 |
154 | test('options: wrong type', () => {
155 | expect(() => {
156 | (HtmlExtractors.elementContent)('[translate]', 'foo');
157 | }).toThrowError(`Argument 'options' must be an object`);
158 | });
159 |
160 | test('options.attributes: wrong type', () => {
161 | expect(() => {
162 | (HtmlExtractors.elementContent)('[translate]', {
163 | attributes: 'foo'
164 | });
165 | }).toThrowError(`Property 'options.attributes' must be an object`);
166 | });
167 |
168 | test('options.attributes.textPlural: wrong type', () => {
169 | expect(() => {
170 | (HtmlExtractors.elementContent)('[translate]', {
171 | attributes: {
172 | textPlural: 42
173 | }
174 | });
175 | }).toThrowError(`Property 'options.attributes.textPlural' must be a string`);
176 | });
177 |
178 | test('options.attributes.context: wrong type', () => {
179 | expect(() => {
180 | (HtmlExtractors.elementContent)('[translate]', {
181 | attributes: {
182 | context: 42
183 | }
184 | });
185 | }).toThrowError(`Property 'options.attributes.context' must be a string`);
186 | });
187 |
188 | test('options.attributes.comment: wrong type', () => {
189 | expect(() => {
190 | (HtmlExtractors.elementContent)('[translate]', {
191 | attributes: {
192 | comment: 42
193 | }
194 | });
195 | }).toThrowError(`Property 'options.attributes.comment' must be a string`);
196 | });
197 |
198 | test('options.content: wrong type', () => {
199 | expect(() => {
200 | (HtmlExtractors.elementContent)('[translate]', {
201 | content: 'foo'
202 | });
203 | }).toThrowError(`Property 'options.content' must be an object`);
204 | });
205 |
206 | test('options.content.trimWhiteSpace: wrong type', () => {
207 | expect(() => {
208 | (HtmlExtractors.elementContent)('[translate]', {
209 | content: {
210 | trimWhiteSpace: 'foo'
211 | }
212 | });
213 | }).toThrowError(`Property 'options.content.trimWhiteSpace' must be a boolean`);
214 | });
215 |
216 | test('options.content.preserveIndentation: wrong type', () => {
217 | expect(() => {
218 | (HtmlExtractors.elementContent)('[translate]', {
219 | content: {
220 | preserveIndentation: 'foo'
221 | }
222 | });
223 | }).toThrowError(`Property 'options.content.preserveIndentation' must be a boolean`);
224 | });
225 |
226 | test('options.content.replaceNewLines: wrong type', () => {
227 | expect(() => {
228 | (HtmlExtractors.elementContent)('[translate]', {
229 | content: {
230 | replaceNewLines: 42
231 | }
232 | });
233 | }).toThrowError(`Property 'options.content.replaceNewLines' must be false or a string`);
234 | });
235 |
236 | test('options.content.replaceNewLines: true', () => {
237 | expect(() => {
238 | (HtmlExtractors.elementContent)('[translate]', {
239 | content: {
240 | replaceNewLines: true
241 | }
242 | });
243 | }).toThrowError(`Property 'options.content.replaceNewLines' must be false or a string`);
244 | });
245 | });
246 |
247 | function assertMessages(extractorFunction: IHtmlExtractorFunction, source: string, ...expected: Partial[]): () => void {
248 | return () => {
249 | let extractor = new GettextExtractor();
250 |
251 | extractor.createHtmlParser([extractorFunction]).parseString(source);
252 |
253 | expect(extractor.getMessages()).toStrictEqual(
254 | expected.map(message => ({
255 | textPlural: null,
256 | context: null,
257 | comments: [],
258 | references: [],
259 | ...message
260 | }))
261 | );
262 | };
263 | }
264 |
--------------------------------------------------------------------------------
/tests/e2e/html/embeddedJs.test.ts:
--------------------------------------------------------------------------------
1 | import { HtmlExtractors } from '../../../dist';
2 |
3 | describe('argument validation', () => {
4 |
5 | test('selector: (none)', () => {
6 | expect(() => {
7 | (HtmlExtractors.embeddedJs)();
8 | }).toThrowError(`Missing argument 'selector'`);
9 | });
10 |
11 | test('selector: null', () => {
12 | expect(() => {
13 | (HtmlExtractors.embeddedJs)(null);
14 | }).toThrowError(`Argument 'selector' must be a non-empty string`);
15 | });
16 |
17 | test('selector: wrong type', () => {
18 | expect(() => {
19 | (HtmlExtractors.embeddedJs)(42);
20 | }).toThrowError(`Argument 'selector' must be a non-empty string`);
21 | });
22 |
23 | test('jsParser: (none)', () => {
24 | expect(() => {
25 | (HtmlExtractors.embeddedJs)('script');
26 | }).toThrowError(`Missing argument 'jsParser'`);
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/tests/e2e/js.test.ts:
--------------------------------------------------------------------------------
1 | import { GettextExtractor, JsExtractors } from '../../dist';
2 | import * as fs from 'fs';
3 |
4 | describe('JavaScript E2E', () => {
5 |
6 | test('example', () => {
7 | let extractor = new GettextExtractor();
8 |
9 | extractor
10 | .createJsParser([
11 | JsExtractors.callExpression(['t', '[this].translations.get'], {
12 | arguments: {
13 | text: 0,
14 | context: 1
15 | }
16 | }),
17 | JsExtractors.callExpression('[this].translations.plural', {
18 | arguments: {
19 | text: 1,
20 | textPlural: 2,
21 | context: 3
22 | }
23 | })
24 | ])
25 | .parseFilesGlob('tests/e2e/fixtures/js/**/*.@(js|jsx)');
26 |
27 | expect(extractor.getPotString()).toBe(fs.readFileSync(__dirname + '/fixtures/js/example.expected.pot').toString());
28 | });
29 |
30 | test('multi-line', () => {
31 | let extractor = new GettextExtractor();
32 |
33 | extractor
34 | .createJsParser([
35 | JsExtractors.callExpression(['translate'], {
36 | arguments: {
37 | text: 0
38 | }
39 | }),
40 | JsExtractors.callExpression(['translate_trim'], {
41 | arguments: {
42 | text: 0
43 | },
44 | content: {
45 | trimWhiteSpace: true,
46 | preserveIndentation: false
47 | }
48 | })
49 | ])
50 | .parseFilesGlob('tests/e2e/fixtures/js/**/*.js');
51 |
52 | expect(extractor.getPotString()).toBe(fs.readFileSync(__dirname + '/fixtures/js/multiline.expected.pot').toString());
53 | });
54 | });
55 |
--------------------------------------------------------------------------------
/tests/extractor.test.ts:
--------------------------------------------------------------------------------
1 | import { GettextExtractor } from '../src/extractor';
2 |
3 | describe('GettextExtractor', () => {
4 |
5 | let extractor: GettextExtractor;
6 |
7 | beforeEach(() => {
8 | extractor = new GettextExtractor();
9 | });
10 |
11 | describe('POT string', () => {
12 |
13 | test('default headers', () => {
14 | let pot = extractor.getPotString();
15 | expect(pot).toBe(
16 | `msgid ""\n` +
17 | `msgstr ""\n` +
18 | `"Content-Type: text/plain; charset=UTF-8\\n"\n`
19 | );
20 | });
21 |
22 | test('additional headers', () => {
23 | let pot = extractor.getPotString({
24 | 'Project-Id-Version': 'Foo'
25 | });
26 | expect(pot).toBe(
27 | `msgid ""\n` +
28 | `msgstr ""\n` +
29 | `"Content-Type: text/plain; charset=UTF-8\\n"\n` +
30 | `"Project-Id-Version: Foo\\n"\n`
31 | );
32 | });
33 |
34 | test('overridden content type header', () => {
35 | let pot = extractor.getPotString({
36 | 'Content-Type': 'text/plain; charset=ISO-8859-1'
37 | });
38 | expect(pot).toBe(
39 | `msgid ""\n` +
40 | `msgstr ""\n` +
41 | `"Content-Type: text/plain; charset=ISO-8859-1\\n"\n`
42 | );
43 | });
44 | });
45 |
46 | describe('argument validation', () => {
47 |
48 | describe('addMessage', () => {
49 |
50 | test('message: (none)', () => {
51 | expect(() => {
52 | (extractor.addMessage)();
53 | }).toThrowError(`Missing argument 'message'`);
54 | });
55 |
56 | test('message: null', () => {
57 | expect(() => {
58 | (extractor.addMessage)(null);
59 | }).toThrowError(`Argument 'message' must be an object`);
60 | });
61 |
62 | test('message: wrong type', () => {
63 | expect(() => {
64 | (extractor.addMessage)(42);
65 | }).toThrowError(`Argument 'message' must be an object`);
66 | });
67 |
68 | test('message.text: (none)', () => {
69 | expect(() => {
70 | (extractor.addMessage)({});
71 | }).toThrowError(`Property 'message.text' is missing`);
72 | });
73 |
74 | test('message.text: null', () => {
75 | expect(() => {
76 | (extractor.addMessage)({
77 | text: null
78 | });
79 | }).toThrowError(`Property 'message.text' must be a string`);
80 | });
81 |
82 | test('message.text: wrong type', () => {
83 | expect(() => {
84 | (extractor.addMessage)({
85 | text: 42
86 | });
87 | }).toThrowError(`Property 'message.text' must be a string`);
88 | });
89 |
90 | test('message: only required', () => {
91 | expect(() => {
92 | (extractor.addMessage)({
93 | text: 'Foo'
94 | });
95 | }).not.toThrow();
96 | });
97 |
98 | test('message.textPlural: null', () => {
99 | expect(() => {
100 | (extractor.addMessage)({
101 | text: 'Foo',
102 | textPlural: null
103 | });
104 | }).toThrowError(`Property 'message.textPlural' must be a string`);
105 | });
106 |
107 | test('message.textPlural: wrong type', () => {
108 | expect(() => {
109 | (extractor.addMessage)({
110 | text: 'Foo',
111 | textPlural: 42
112 | });
113 | }).toThrowError(`Property 'message.textPlural' must be a string`);
114 | });
115 |
116 | test('message.context: null', () => {
117 | expect(() => {
118 | (extractor.addMessage)({
119 | text: 'Foo',
120 | context: null
121 | });
122 | }).toThrowError(`Property 'message.context' must be a string`);
123 | });
124 |
125 | test('message.context: wrong type', () => {
126 | expect(() => {
127 | (extractor.addMessage)({
128 | text: 'Foo',
129 | context: 42
130 | });
131 | }).toThrowError(`Property 'message.context' must be a string`);
132 | });
133 |
134 | test('message.references: null', () => {
135 | expect(() => {
136 | (extractor.addMessage)({
137 | text: 'Foo',
138 | references: null
139 | });
140 | }).toThrowError(`Property 'message.references' must be an array`);
141 | });
142 |
143 | test('message.references: wrong type', () => {
144 | expect(() => {
145 | (extractor.addMessage)({
146 | text: 'Foo',
147 | references: 42
148 | });
149 | }).toThrowError(`Property 'message.references' must be an array`);
150 | });
151 |
152 | test('message.comments: null', () => {
153 | expect(() => {
154 | (extractor.addMessage)({
155 | text: 'Foo',
156 | comments: null
157 | });
158 | }).toThrowError(`Property 'message.comments' must be an array`);
159 | });
160 |
161 | test('message.comments: wrong type', () => {
162 | expect(() => {
163 | (extractor.addMessage)({
164 | text: 'Foo',
165 | comments: 42
166 | });
167 | }).toThrowError(`Property 'message.comments' must be an array`);
168 | });
169 | });
170 |
171 | describe('getPotString', () => {
172 |
173 | test('headers: wrong type', () => {
174 | expect(() => {
175 | (extractor.getPotString)('foo');
176 | }).toThrowError(`Argument 'headers' must be an object`);
177 | });
178 | });
179 |
180 | describe('savePotFile', () => {
181 |
182 | test('fileName: (none)', () => {
183 | expect(() => {
184 | (extractor.savePotFile)();
185 | }).toThrowError(`Missing argument 'fileName'`);
186 | });
187 |
188 | test('fileName: null', () => {
189 | expect(() => {
190 | (extractor.savePotFile)(null);
191 | }).toThrowError(`Argument 'fileName' must be a non-empty string`);
192 | });
193 |
194 | test('fileName: wrong type', () => {
195 | expect(() => {
196 | (extractor.savePotFile)(42);
197 | }).toThrowError(`Argument 'fileName' must be a non-empty string`);
198 | });
199 |
200 | test('headers: wrong type', () => {
201 | expect(() => {
202 | (extractor.savePotFile)('foo.ts', 'foo');
203 | }).toThrowError(`Argument 'headers' must be an object`);
204 | });
205 | });
206 |
207 | describe('savePotFileAsync', () => {
208 |
209 | test('fileName: (none)', () => {
210 | expect(() => {
211 | (extractor.savePotFileAsync)();
212 | }).toThrowError(`Missing argument 'fileName'`);
213 | });
214 |
215 | test('fileName: null', () => {
216 | expect(() => {
217 | (extractor.savePotFileAsync)(null);
218 | }).toThrowError(`Argument 'fileName' must be a non-empty string`);
219 | });
220 |
221 | test('fileName: wrong type', () => {
222 | expect(() => {
223 | (extractor.savePotFileAsync)(42);
224 | }).toThrowError(`Argument 'fileName' must be a non-empty string`);
225 | });
226 |
227 | test('headers: wrong type', () => {
228 | expect(() => {
229 | (extractor.savePotFileAsync)('foo.ts', 'foo');
230 | }).toThrowError(`Argument 'headers' must be an object`);
231 | });
232 | });
233 |
234 | describe('createJsParser', () => {
235 |
236 | test('extractors: (none)', () => {
237 | expect(() => {
238 | (extractor.createJsParser)();
239 | }).not.toThrow();
240 | });
241 |
242 | test('extractors: null', () => {
243 | expect(() => {
244 | (extractor.createJsParser)(null);
245 | }).toThrowError(`Argument 'extractors' must be a non-empty array`);
246 | });
247 |
248 | test('extractors: wrong type', () => {
249 | expect(() => {
250 | (extractor.createJsParser)(42);
251 | }).toThrowError(`Argument 'extractors' must be a non-empty array`);
252 | });
253 |
254 | test('extractors: []', () => {
255 | expect(() => {
256 | (extractor.createJsParser)([]);
257 | }).toThrowError(`Argument 'extractors' must be a non-empty array`);
258 | });
259 |
260 | test('extractors: [(none)]', () => {
261 | expect(() => {
262 | (extractor.createJsParser)([
263 | undefined
264 | ]);
265 | }).toThrowError(`Invalid extractor function provided. 'undefined' is not a function`);
266 | });
267 |
268 | test('extractors: [null]', () => {
269 | expect(() => {
270 | (extractor.createJsParser)([
271 | null
272 | ]);
273 | }).toThrowError(`Invalid extractor function provided. 'null' is not a function`);
274 | });
275 |
276 | test('extractors: [wrong type]', () => {
277 | expect(() => {
278 | (extractor.createJsParser)([
279 | 42
280 | ]);
281 | }).toThrowError(`Invalid extractor function provided. '42' is not a function`);
282 | });
283 |
284 | test('extractors: [function, wrong type]', () => {
285 | expect(() => {
286 | (extractor.createJsParser)([
287 | () => {},
288 | 42
289 | ]);
290 | }).toThrowError(`Invalid extractor function provided. '42' is not a function`);
291 | });
292 | });
293 | });
294 | });
295 |
--------------------------------------------------------------------------------
/tests/fixtures/directory.ts/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lukasgeiter/gettext-extractor/1688fa8b6880cb82501879cf6e95cd52db47aaa4/tests/fixtures/directory.ts/.gitkeep
--------------------------------------------------------------------------------
/tests/fixtures/empty.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lukasgeiter/gettext-extractor/1688fa8b6880cb82501879cf6e95cd52db47aaa4/tests/fixtures/empty.ts
--------------------------------------------------------------------------------
/tests/fixtures/unicode.ts:
--------------------------------------------------------------------------------
1 | // Source: http://www.cl.cam.ac.uk/~mgk25/ucs/examples/quickbrown.txt
2 |
3 | export const UnicodeSamples = {
4 | danish: `Quizdeltagerne spiste jordbær med fløde, mens cirkusklovnen Wolther spillede på xylofon`,
5 | german: `Falsches Üben von Xylophonmusik quält jeden größeren Zwerg - Zwölf Boxkämpfer jagten Eva quer über den Sylter Deich - Heizölrückstoßabdämpfung`,
6 | greek: `Γαζέες καὶ μυρτιὲς δὲν θὰ βρῶ πιὰ στὸ χρυσαφὶ ξέφωτο - Ξεσκεπάζω τὴν ψυχοφθόρα βδελυγμία`,
7 | english: `The quick brown fox jumps over the lazy dog`,
8 | spanish: `El pingüino Wenceslao hizo kilómetros bajo exhaustiva lluvia y frío, añoraba a su querido cachorro`,
9 | french: `Portez ce vieux whisky au juge blond qui fume sur son île intérieure, à côté de l'alcôve ovoïde, où les bûches se consument dans l'âtre, ce qui lui permet de penser à la cænogenèse de l'être dont il est question dans la cause ambiguë entendue à Moÿ, dans un capharnaüm qui, pense-t-il, diminue çà et là la qualité de son œuvre - Le cœur déçu mais l'âme plutôt naïve, Louÿs rêva de crapaüter en canoë au delà des îles, près du mälström où brûlent les novæ`,
10 | irishGaelic: `D'fhuascail Íosa, Úrmhac na hÓighe Beannaithe, pór Éava agus Ádhaimh`,
11 | hungarian: `Árvíztűrő tükörfúrógép`,
12 | icelandic: `Kæmi ný öxi hér ykist þjófum nú bæði víl og ádrepa - Sævör grét áðan því úlpan var ónýt`,
13 | japanese: `いろはにほへとちりぬるを わかよたれそつねならむ うゐのおくやまけふこえて あさきゆめみしゑひもせす - イロハニホヘト チリヌルヲ ワカヨタレソ ツネナラム ウヰノオクヤマ ケフコエテ アサキユメミシ ヱヒモセスン`,
14 | hebrew: `דג סקרן שט בים מאוכזב ולפתע מצא לו חברה איך הקליטה`,
15 | polish: `Pchnąć w tę łódź jeża lub ośm skrzyń fig`,
16 | russian: `В чащах юга жил бы цитрус? Да, но фальшивый экземпляр! - Съешь же ещё этих мягких французских булок да выпей чаю`,
17 | thai: `๏ เป็นมนุษย์สุดประเสริฐเลิศคุณค่า กว่าบรรดาฝูงสัตว์เดรัจฉาน จงฝ่าฟันพัฒนาวิชาการ อย่าล้างผลาญฤๅเข่นฆ่าบีฑาใคร ไม่ถือโทษโกรธแช่งซัดฮึดฮัดด่า หัดอภัยเหมือนกีฬาอัชฌาสัย ปฏิบัติประพฤติกฎกำหนดใจ พูดจาให้จ๊ะๆ จ๋าๆ น่าฟังเอย ฯ`,
18 | turkish: `Pijamalı hasta, yağız şoföre çabucak güvendi`
19 | };
20 |
21 | export function createUnicodeTests(callback: (text: string) => void): void {
22 | for (let [language, text] of Object.entries(UnicodeSamples)) {
23 | test(language, () => {
24 | callback(text);
25 | });
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/html/extractors/factories/elementAttribute.test.ts:
--------------------------------------------------------------------------------
1 | import { HtmlParser } from '../../../../src/html/parser';
2 | import { CatalogBuilder, IMessage } from '../../../../src/builder';
3 | import { elementAttributeExtractor } from '../../../../src/html/extractors/factories/elementAttribute';
4 | import { elementContentExtractor } from '../../../../src/html/extractors/factories/elementContent';
5 |
6 | describe('HTML: Element Attribute Extractor', () => {
7 |
8 | let builder: CatalogBuilder,
9 | messages: IMessage[],
10 | parser: HtmlParser;
11 |
12 | beforeEach(() => {
13 | messages = [];
14 |
15 | builder = {
16 | addMessage: jest.fn((message: IMessage) => {
17 | messages.push(message);
18 | })
19 | };
20 | });
21 |
22 | describe('standard', () => {
23 |
24 | beforeEach(() => {
25 | parser = new HtmlParser(builder, [
26 | elementAttributeExtractor('translate', 'text', {
27 | attributes: {
28 | context: 'context',
29 | textPlural: 'plural'
30 | }
31 | })
32 | ]);
33 | });
34 |
35 | test('just text', () => {
36 | parser.parseString(``);
37 |
38 | expect(messages).toEqual([
39 | {
40 | text: 'Foo'
41 | }
42 | ]);
43 | });
44 |
45 | test('with context', () => {
46 | parser.parseString(``);
47 |
48 | expect(messages).toEqual([
49 | {
50 | text: 'Foo',
51 | context: 'Context'
52 | }
53 | ]);
54 | });
55 |
56 | test('plural', () => {
57 | parser.parseString(``);
58 |
59 | expect(messages).toEqual([
60 | {
61 | text: 'Foo',
62 | textPlural: 'Foos'
63 | }
64 | ]);
65 | });
66 |
67 | test('plural with context', () => {
68 | parser.parseString(``);
69 |
70 | expect(messages).toEqual([
71 | {
72 | text: 'Foo',
73 | textPlural: 'Foos',
74 | context: 'Context'
75 | }
76 | ]);
77 | });
78 |
79 | test('missing text', () => {
80 | parser.parseString(``);
81 |
82 | expect(messages).toEqual([]);
83 | });
84 | });
85 |
86 | describe('just text', () => {
87 |
88 | beforeEach(() => {
89 | parser = new HtmlParser(builder, [
90 | elementAttributeExtractor('translate', 'text')
91 | ]);
92 | });
93 |
94 | test('with context', () => {
95 | parser.parseString(``);
96 |
97 | expect(messages).toEqual([
98 | {
99 | text: 'Foo'
100 | }
101 | ]);
102 | });
103 |
104 | test('plural', () => {
105 | parser.parseString(``);
106 |
107 | expect(messages).toEqual([
108 | {
109 | text: 'Foo'
110 | }
111 | ]);
112 | });
113 |
114 | test('plural with context', () => {
115 | parser.parseString(``);
116 |
117 | expect(messages).toEqual([
118 | {
119 | text: 'Foo'
120 | }
121 | ]);
122 | });
123 | });
124 |
125 | describe('comment', () => {
126 |
127 | beforeEach(() => {
128 | parser = new HtmlParser(builder, [
129 | elementAttributeExtractor('translate', 'text', {
130 | attributes: {
131 | comment: 'comment'
132 | }
133 | })
134 | ]);
135 | });
136 |
137 | test('just text', () => {
138 | parser.parseString(``);
139 |
140 | expect(messages).toEqual([
141 | {
142 | text: 'Foo'
143 | }
144 | ]);
145 | });
146 |
147 | test('with comment', () => {
148 | parser.parseString(``);
149 |
150 | expect(messages).toEqual([
151 | {
152 | text: 'Foo',
153 | comments: [
154 | 'Foo Bar'
155 | ]
156 | }
157 | ]);
158 | });
159 |
160 | test('empty comment', () => {
161 | parser.parseString(``);
162 |
163 | expect(messages).toEqual([
164 | {
165 | text: 'Foo'
166 | }
167 | ]);
168 | });
169 | });
170 |
171 | describe('argument validation', () => {
172 |
173 | test('selector: (none)', () => {
174 | expect(() => {
175 | (elementAttributeExtractor)();
176 | }).toThrowError(`Missing argument 'selector'`);
177 | });
178 |
179 | test('selector: null', () => {
180 | expect(() => {
181 | (elementAttributeExtractor)(null);
182 | }).toThrowError(`Argument 'selector' must be a non-empty string`);
183 | });
184 |
185 | test('selector: wrong type', () => {
186 | expect(() => {
187 | (elementAttributeExtractor)(42);
188 | }).toThrowError(`Argument 'selector' must be a non-empty string`);
189 | });
190 |
191 | test('textAttribute: (none)', () => {
192 | expect(() => {
193 | (elementAttributeExtractor)('[translate]');
194 | }).toThrowError(`Missing argument 'textAttribute'`);
195 | });
196 |
197 | test('textAttribute: null', () => {
198 | expect(() => {
199 | (elementAttributeExtractor)('[translate]', null);
200 | }).toThrowError(`Argument 'textAttribute' must be a non-empty string`);
201 | });
202 |
203 | test('textAttribute: wrong type', () => {
204 | expect(() => {
205 | (elementAttributeExtractor)('[translate]', 42);
206 | }).toThrowError(`Argument 'textAttribute' must be a non-empty string`);
207 | });
208 |
209 | test('options: wrong type', () => {
210 | expect(() => {
211 | (elementAttributeExtractor)('[translate]', 'translate', 'foo');
212 | }).toThrowError(`Argument 'options' must be an object`);
213 | });
214 |
215 | test('options.attributes: wrong type', () => {
216 | expect(() => {
217 | (elementAttributeExtractor)('[translate]', 'translate', {
218 | attributes: 'foo'
219 | });
220 | }).toThrowError(`Property 'options.attributes' must be an object`);
221 | });
222 |
223 | test('options.attributes.textPlural: wrong type', () => {
224 | expect(() => {
225 | (elementAttributeExtractor)('[translate]', 'translate', {
226 | attributes: {
227 | textPlural: 42
228 | }
229 | });
230 | }).toThrowError(`Property 'options.attributes.textPlural' must be a string`);
231 | });
232 |
233 | test('options.attributes.context: wrong type', () => {
234 | expect(() => {
235 | (elementAttributeExtractor)('[translate]', 'translate', {
236 | attributes: {
237 | context: 42
238 | }
239 | });
240 | }).toThrowError(`Property 'options.attributes.context' must be a string`);
241 | });
242 |
243 | test('options.attributes.comment: wrong type', () => {
244 | expect(() => {
245 | (elementAttributeExtractor)('[translate]', 'translate', {
246 | attributes: {
247 | comment: 42
248 | }
249 | });
250 | }).toThrowError(`Property 'options.attributes.comment' must be a string`);
251 | });
252 |
253 | test('options.content: wrong type', () => {
254 | expect(() => {
255 | (elementContentExtractor)('[translate]', {
256 | content: 'foo'
257 | });
258 | }).toThrowError(`Property 'options.content' must be an object`);
259 | });
260 |
261 | test('options.content.trimWhiteSpace: wrong type', () => {
262 | expect(() => {
263 | (elementContentExtractor)('[translate]', {
264 | content: {
265 | trimWhiteSpace: 'foo'
266 | }
267 | });
268 | }).toThrowError(`Property 'options.content.trimWhiteSpace' must be a boolean`);
269 | });
270 |
271 | test('options.content.preserveIndentation: wrong type', () => {
272 | expect(() => {
273 | (elementContentExtractor)('[translate]', {
274 | content: {
275 | preserveIndentation: 'foo'
276 | }
277 | });
278 | }).toThrowError(`Property 'options.content.preserveIndentation' must be a boolean`);
279 | });
280 |
281 | test('options.content.replaceNewLines: wrong type', () => {
282 | expect(() => {
283 | (elementContentExtractor)('[translate]', {
284 | content: {
285 | replaceNewLines: 42
286 | }
287 | });
288 | }).toThrowError(`Property 'options.content.replaceNewLines' must be false or a string`);
289 | });
290 |
291 | test('options.content.replaceNewLines: true', () => {
292 | expect(() => {
293 | (elementContentExtractor)('[translate]', {
294 | content: {
295 | replaceNewLines: true
296 | }
297 | });
298 | }).toThrowError(`Property 'options.content.replaceNewLines' must be false or a string`);
299 | });
300 | });
301 |
302 | describe('argument proxying', () => {
303 | test('options.content.options: applies for all attributes', () => {
304 | parser = new HtmlParser(builder, [
305 | elementAttributeExtractor('translate', 'text', {
306 | attributes: {
307 | textPlural: 'plural',
308 | context: 'context',
309 | comment: 'comment'
310 | },
311 | content: {
312 | preserveIndentation: false,
313 | replaceNewLines: '',
314 | trimWhiteSpace: true
315 | }
316 | })
317 | ]);
318 |
319 | parser.parseString(`
320 |
333 | `);
334 |
335 | expect(messages).toEqual([
336 | {
337 | text: 'Foo',
338 | textPlural: 'Foos',
339 | context: 'Context',
340 | comments: ['Comment']
341 | }
342 | ]);
343 | });
344 | });
345 | });
346 |
--------------------------------------------------------------------------------
/tests/html/extractors/factories/elementContent.test.ts:
--------------------------------------------------------------------------------
1 | import { HtmlParser } from '../../../../src/html/parser';
2 | import { CatalogBuilder, IMessage } from '../../../../src/builder';
3 | import { elementContentExtractor } from '../../../../src/html/extractors/factories/elementContent';
4 |
5 | describe('HTML: Element Content Extractor', () => {
6 |
7 | let builder: CatalogBuilder,
8 | messages: IMessage[],
9 | parser: HtmlParser;
10 |
11 | beforeEach(() => {
12 | messages = [];
13 |
14 | builder = {
15 | addMessage: jest.fn((message: IMessage) => {
16 | messages.push(message);
17 | })
18 | };
19 | });
20 |
21 | describe('standard', () => {
22 |
23 | beforeEach(() => {
24 | parser = new HtmlParser(builder, [
25 | elementContentExtractor('translate', {
26 | attributes: {
27 | context: 'context',
28 | textPlural: 'plural'
29 | }
30 | })
31 | ]);
32 | });
33 |
34 | test('just text', () => {
35 | parser.parseString(`Foo`);
36 |
37 | expect(messages).toEqual([
38 | {
39 | text: 'Foo'
40 | }
41 | ]);
42 | });
43 |
44 | test('with context', () => {
45 | parser.parseString(`Foo`);
46 |
47 | expect(messages).toEqual([
48 | {
49 | text: 'Foo',
50 | context: 'Context'
51 | }
52 | ]);
53 | });
54 |
55 | test('plural', () => {
56 | parser.parseString(`Foo`);
57 |
58 | expect(messages).toEqual([
59 | {
60 | text: 'Foo',
61 | textPlural: 'Foos'
62 | }
63 | ]);
64 | });
65 |
66 | test('plural with context', () => {
67 | parser.parseString(`Foo`);
68 |
69 | expect(messages).toEqual([
70 | {
71 | text: 'Foo',
72 | textPlural: 'Foos',
73 | context: 'Context'
74 | }
75 | ]);
76 | });
77 | });
78 |
79 | describe('just text', () => {
80 |
81 | beforeEach(() => {
82 | parser = new HtmlParser(builder, [
83 | elementContentExtractor('translate')
84 | ]);
85 | });
86 |
87 | test('with context', () => {
88 | parser.parseString(`Foo`);
89 |
90 | expect(messages).toEqual([
91 | {
92 | text: 'Foo'
93 | }
94 | ]);
95 | });
96 |
97 | test('plural', () => {
98 | parser.parseString(`Foo`);
99 |
100 | expect(messages).toEqual([
101 | {
102 | text: 'Foo'
103 | }
104 | ]);
105 | });
106 |
107 | test('plural with context', () => {
108 | parser.parseString(`Foo`);
109 |
110 | expect(messages).toEqual([
111 | {
112 | text: 'Foo'
113 | }
114 | ]);
115 | });
116 | });
117 |
118 | describe('comment', () => {
119 |
120 | beforeEach(() => {
121 | parser = new HtmlParser(builder, [
122 | elementContentExtractor('translate', {
123 | attributes: {
124 | comment: 'comment'
125 | }
126 | })
127 | ]);
128 | });
129 |
130 | test('just text', () => {
131 | parser.parseString(`Foo`);
132 |
133 | expect(messages).toEqual([
134 | {
135 | text: 'Foo'
136 | }
137 | ]);
138 | });
139 |
140 | test('with comment', () => {
141 | parser.parseString(`Foo`);
142 |
143 | expect(messages).toEqual([
144 | {
145 | text: 'Foo',
146 | comments: [
147 | 'Foo Bar'
148 | ]
149 | }
150 | ]);
151 | });
152 |
153 | test('empty comment', () => {
154 | parser.parseString(`Foo`);
155 |
156 | expect(messages).toEqual([
157 | {
158 | text: 'Foo'
159 | }
160 | ]);
161 | });
162 | });
163 |
164 | describe('argument validation', () => {
165 |
166 | test('selector: (none)', () => {
167 | expect(() => {
168 | (elementContentExtractor)();
169 | }).toThrowError(`Missing argument 'selector'`);
170 | });
171 |
172 | test('selector: null', () => {
173 | expect(() => {
174 | (elementContentExtractor)(null);
175 | }).toThrowError(`Argument 'selector' must be a non-empty string`);
176 | });
177 |
178 | test('selector: wrong type', () => {
179 | expect(() => {
180 | (elementContentExtractor)(42);
181 | }).toThrowError(`Argument 'selector' must be a non-empty string`);
182 | });
183 |
184 | test('options: wrong type', () => {
185 | expect(() => {
186 | (elementContentExtractor)('[translate]', 'foo');
187 | }).toThrowError(`Argument 'options' must be an object`);
188 | });
189 |
190 | test('options.attributes: wrong type', () => {
191 | expect(() => {
192 | (elementContentExtractor)('[translate]', {
193 | attributes: 'foo'
194 | });
195 | }).toThrowError(`Property 'options.attributes' must be an object`);
196 | });
197 |
198 | test('options.attributes.textPlural: wrong type', () => {
199 | expect(() => {
200 | (elementContentExtractor)('[translate]', {
201 | attributes: {
202 | textPlural: 42
203 | }
204 | });
205 | }).toThrowError(`Property 'options.attributes.textPlural' must be a string`);
206 | });
207 |
208 | test('options.attributes.context: wrong type', () => {
209 | expect(() => {
210 | (elementContentExtractor)('[translate]', {
211 | attributes: {
212 | context: 42
213 | }
214 | });
215 | }).toThrowError(`Property 'options.attributes.context' must be a string`);
216 | });
217 |
218 | test('options.attributes.comment: wrong type', () => {
219 | expect(() => {
220 | (elementContentExtractor)('[translate]', {
221 | attributes: {
222 | comment: 42
223 | }
224 | });
225 | }).toThrowError(`Property 'options.attributes.comment' must be a string`);
226 | });
227 |
228 | test('options.content: wrong type', () => {
229 | expect(() => {
230 | (elementContentExtractor)('[translate]', {
231 | content: 'foo'
232 | });
233 | }).toThrowError(`Property 'options.content' must be an object`);
234 | });
235 |
236 | test('options.content.trimWhiteSpace: wrong type', () => {
237 | expect(() => {
238 | (elementContentExtractor)('[translate]', {
239 | content: {
240 | trimWhiteSpace: 'foo'
241 | }
242 | });
243 | }).toThrowError(`Property 'options.content.trimWhiteSpace' must be a boolean`);
244 | });
245 |
246 | test('options.content.preserveIndentation: wrong type', () => {
247 | expect(() => {
248 | (elementContentExtractor)('[translate]', {
249 | content: {
250 | preserveIndentation: 'foo'
251 | }
252 | });
253 | }).toThrowError(`Property 'options.content.preserveIndentation' must be a boolean`);
254 | });
255 |
256 | test('options.content.replaceNewLines: wrong type', () => {
257 | expect(() => {
258 | (elementContentExtractor)('[translate]', {
259 | content: {
260 | replaceNewLines: 42
261 | }
262 | });
263 | }).toThrowError(`Property 'options.content.replaceNewLines' must be false or a string`);
264 | });
265 |
266 | test('options.content.replaceNewLines: true', () => {
267 | expect(() => {
268 | (elementContentExtractor)('[translate]', {
269 | content: {
270 | replaceNewLines: true
271 | }
272 | });
273 | }).toThrowError(`Property 'options.content.replaceNewLines' must be false or a string`);
274 | });
275 | });
276 |
277 | describe('argument proxying', () => {
278 | test('options.content.options: applies for all attributes', () => {
279 | parser = new HtmlParser(builder, [
280 | elementContentExtractor('translate', {
281 | attributes: {
282 | textPlural: 'plural',
283 | context: 'context',
284 | comment: 'comment'
285 | },
286 | content: {
287 | preserveIndentation: false,
288 | replaceNewLines: '',
289 | trimWhiteSpace: true
290 | }
291 | })
292 | ]);
293 |
294 | parser.parseString(`
295 |
305 | Foo
306 |
307 | `);
308 |
309 | expect(messages).toEqual([
310 | {
311 | text: 'Foo',
312 | textPlural: 'Foos',
313 | context: 'Context',
314 | comments: ['Comment']
315 | }
316 | ]);
317 | });
318 | });
319 | });
320 |
--------------------------------------------------------------------------------
/tests/html/extractors/factories/embeddedAttributeJs.test.ts:
--------------------------------------------------------------------------------
1 | import { embeddedAttributeJsExtractor } from '../../../../src/html/extractors/factories/embeddedAttributeJs';
2 | import { HtmlParser } from '../../../../src/html/parser';
3 | import { JsParser } from '../../../../src/js/parser';
4 |
5 | describe('HTML: Attribute Value as Embedded JS Extractor', () => {
6 | describe('calling js parser', () => {
7 | let jsParserMock: JsParser;
8 |
9 | beforeEach(() => {
10 | jsParserMock = {
11 | parseString: jest.fn(),
12 | };
13 | });
14 |
15 | test('use regex filter / with line number start', () => {
16 | const htmlParser = new HtmlParser(undefined!, [
17 | embeddedAttributeJsExtractor(/:title/, jsParserMock),
18 | ]);
19 | htmlParser.parseString(
20 | `content`,
21 | 'foo.html',
22 | { lineNumberStart: 10 }
23 | );
24 | expect(jsParserMock.parseString).toHaveBeenCalledWith(
25 | `__('msg id')`,
26 | 'foo.html',
27 | {
28 | lineNumberStart: 10,
29 | }
30 | );
31 | });
32 |
33 | test('use filter function', () => {
34 | const htmlParser = new HtmlParser(undefined!, [
35 | embeddedAttributeJsExtractor((e) => {
36 | return e.name.startsWith(':');
37 | }, jsParserMock),
38 | ]);
39 | htmlParser.parseString(
40 | `Hello`,
41 | 'foo.html'
42 | );
43 | expect(jsParserMock.parseString).toHaveBeenCalledWith(
44 | `__('title')`,
45 | 'foo.html',
46 | { lineNumberStart: 1 }
47 | );
48 | });
49 | });
50 |
51 | describe('argument validation', () => {
52 | test('filter: (none)', () => {
53 | expect(() => {
54 | (embeddedAttributeJsExtractor)();
55 | }).toThrowError(`Missing argument 'filter'`);
56 | });
57 | test('jsParser: (none)', () => {
58 | expect(() => {
59 | (embeddedAttributeJsExtractor)(/:title/);
60 | }).toThrowError(`Missing argument 'jsParser'`);
61 | });
62 | });
63 | });
64 |
--------------------------------------------------------------------------------
/tests/html/extractors/factories/embeddedJs.test.ts:
--------------------------------------------------------------------------------
1 | import { HtmlParser } from '../../../../src/html/parser';
2 | import { embeddedJsExtractor } from '../../../../src/html/extractors/factories/embeddedJs';
3 | import { JsParser } from '../../../../src/js/parser';
4 |
5 | describe('HTML: Embedded JS Extractor', () => {
6 |
7 | describe('calling js parser', () => {
8 |
9 | let htmlParser: HtmlParser,
10 | jsParserMock: JsParser;
11 |
12 | beforeEach(() => {
13 | jsParserMock = {
14 | parseString: jest.fn()
15 | };
16 |
17 | htmlParser = new HtmlParser(undefined!, [
18 | embeddedJsExtractor('script', jsParserMock)
19 | ]);
20 | });
21 |
22 | test('single line', () => {
23 | htmlParser.parseString(``, 'foo.html');
24 |
25 | expect(jsParserMock.parseString).toHaveBeenCalledWith('Foo', 'foo.html', {
26 | lineNumberStart: 1
27 | });
28 | });
29 |
30 | test('with lineNumberStart option', () => {
31 | htmlParser.parseString(``, 'foo.html', { lineNumberStart: 10 });
32 |
33 | expect(jsParserMock.parseString).toHaveBeenCalledWith('Foo', 'foo.html', {
34 | lineNumberStart: 10
35 | });
36 | });
37 |
38 | test('separate line', () => {
39 | htmlParser.parseString(``, 'foo.html');
40 |
41 | expect(jsParserMock.parseString).toHaveBeenCalledWith('\nFoo\n', 'foo.html', {
42 | lineNumberStart: 1
43 | });
44 | });
45 |
46 | test('offset', () => {
47 | htmlParser.parseString(`\n
Hello World
\n\n\n`, 'foo.html');
48 |
49 | expect(jsParserMock.parseString).toHaveBeenCalledWith('Foo', 'foo.html', {
50 | lineNumberStart: 5
51 | });
52 | });
53 | });
54 |
55 | describe('argument validation', () => {
56 |
57 | test('selector: (none)', () => {
58 | expect(() => {
59 | (embeddedJsExtractor)();
60 | }).toThrowError(`Missing argument 'selector'`);
61 | });
62 |
63 | test('selector: null', () => {
64 | expect(() => {
65 | (embeddedJsExtractor)(null);
66 | }).toThrowError(`Argument 'selector' must be a non-empty string`);
67 | });
68 |
69 | test('selector: wrong type', () => {
70 | expect(() => {
71 | (embeddedJsExtractor)(42);
72 | }).toThrowError(`Argument 'selector' must be a non-empty string`);
73 | });
74 |
75 | test('jsParser: (none)', () => {
76 | expect(() => {
77 | (embeddedJsExtractor)('script');
78 | }).toThrowError(`Missing argument 'jsParser'`);
79 | });
80 | });
81 | });
82 |
--------------------------------------------------------------------------------
/tests/html/parser.test.ts:
--------------------------------------------------------------------------------
1 | import { HtmlParser, TextNode, Node } from '../../src/html/parser';
2 | import { registerCommonParserTests } from '../parser.common';
3 | import { UnicodeSamples } from '../fixtures/unicode';
4 | import { CatalogBuilder } from '../../src/builder';
5 | import { IParseOptions } from '../../src/parser';
6 |
7 | describe('HtmlParser', () => {
8 |
9 | registerCommonParserTests(HtmlParser);
10 |
11 | describe('line number', () => {
12 |
13 | let parser: HtmlParser;
14 | let builderMock: CatalogBuilder;
15 |
16 | beforeEach(() => {
17 | builderMock = {
18 | addMessage: jest.fn()
19 | };
20 | parser = new HtmlParser(builderMock, [(node: Node, fileName: string, addMessage) => {
21 | if (node.nodeName === '#text') {
22 | addMessage({
23 | text: (node as TextNode).value
24 | });
25 | }
26 | }]);
27 | });
28 |
29 | test('first line', () => {
30 | parser.parseString(`Foo`, 'foo.html');
31 |
32 | expect(builderMock.addMessage).toHaveBeenCalledWith({
33 | text: 'Foo',
34 | references: ['foo.html:1']
35 | });
36 | });
37 |
38 | test('third line', () => {
39 | parser.parseString(`\n\nFoo`, 'foo.html');
40 |
41 | expect(builderMock.addMessage).toHaveBeenCalledWith({
42 | text: 'Foo',
43 | references: ['foo.html:3']
44 | });
45 | });
46 |
47 | test('with offset', () => {
48 | parser.parseString(`Foo`, 'foo.html', {
49 | lineNumberStart: 10
50 | });
51 |
52 | expect(builderMock.addMessage).toHaveBeenCalledWith({
53 | text: 'Foo',
54 | references: ['foo.html:10']
55 | });
56 | });
57 | });
58 |
59 | test('transform source function', () => {
60 | let parser = new HtmlParser({}, [jest.fn()]);
61 | let parseFunctionMock = (parser).parse = jest.fn(() => []);
62 |
63 | const fileName = 'foo.html';
64 | const parseOptions: IParseOptions = {
65 | transformSource: source => source.toUpperCase()
66 | };
67 |
68 | parser.parseString('foo', fileName, parseOptions);
69 | expect(parseFunctionMock).toHaveBeenCalledWith('FOO', fileName, parseOptions);
70 | });
71 |
72 | describe('unicode', () => {
73 |
74 | function check(text: string): void {
75 | let parser = new HtmlParser(new CatalogBuilder(), [(node: Node) => {
76 | if (node.nodeName === '#text') {
77 | expect((node as TextNode).value).toEqual(text);
78 | }
79 | }]);
80 |
81 | parser.parseString(`${text}`);
82 | }
83 |
84 | test('danish', () => {
85 | check(UnicodeSamples.danish);
86 | });
87 |
88 | test('german', () => {
89 | check(UnicodeSamples.german);
90 | });
91 |
92 | test('greek', () => {
93 | check(UnicodeSamples.greek);
94 | });
95 |
96 | test('english', () => {
97 | check(UnicodeSamples.english);
98 | });
99 |
100 | test('spanish', () => {
101 | check(UnicodeSamples.spanish);
102 | });
103 |
104 | test('french', () => {
105 | check(UnicodeSamples.french);
106 | });
107 |
108 | test('irish gaelic', () => {
109 | check(UnicodeSamples.irishGaelic);
110 | });
111 |
112 | test('hungarian', () => {
113 | check(UnicodeSamples.hungarian);
114 | });
115 |
116 | test('icelandic', () => {
117 | check(UnicodeSamples.icelandic);
118 | });
119 |
120 | test('japanese', () => {
121 | check(UnicodeSamples.japanese);
122 | });
123 |
124 | test('hebrew', () => {
125 | check(UnicodeSamples.hebrew);
126 | });
127 |
128 | test('polish', () => {
129 | check(UnicodeSamples.polish);
130 | });
131 |
132 | test('russian', () => {
133 | check(UnicodeSamples.russian);
134 | });
135 |
136 | test('thai', () => {
137 | check(UnicodeSamples.thai);
138 | });
139 |
140 | test('turkish', () => {
141 | check(UnicodeSamples.turkish);
142 | });
143 | });
144 |
145 | test('template element', () => {
146 | let parser: HtmlParser;
147 | let builderMock: CatalogBuilder;
148 |
149 | builderMock = {
150 | addMessage: jest.fn()
151 | };
152 | parser = new HtmlParser(builderMock, [(node: Node, fileName: string, addMessage) => {
153 | if (node.nodeName === '#text') {
154 | addMessage({
155 | text: (node as TextNode).value
156 | });
157 | }
158 | }]);
159 |
160 | parser.parseString(`Foo`, 'foo.html');
161 |
162 | expect(builderMock.addMessage).toHaveBeenCalledWith({
163 | text: 'Foo',
164 | references: ['foo.html:1']
165 | });
166 | });
167 | });
168 |
--------------------------------------------------------------------------------
/tests/html/utils.test.ts:
--------------------------------------------------------------------------------
1 | import * as parse5 from 'parse5';
2 | import { HtmlUtils } from '../../src/html/utils';
3 | import { Element } from '../../src/html/parser';
4 |
5 | describe('HTML: Utils', () => {
6 |
7 | function createElement(source: string): Element {
8 | return (parse5.parse(source)).childNodes[0].childNodes[1].childNodes[0];
9 | }
10 |
11 | describe('getAttributeValue', () => {
12 |
13 | test('normal attribute value', () => {
14 | expect(HtmlUtils.getAttributeValue(createElement(''), 'foo')).toBe('bar');
15 | });
16 |
17 | test('attribute missing', () => {
18 | expect(HtmlUtils.getAttributeValue(createElement(''), 'foo')).toBe(null);
19 | });
20 |
21 | test('empty string', () => {
22 | expect(HtmlUtils.getAttributeValue(createElement(''), 'foo')).toBe('');
23 | });
24 |
25 | test('no value', () => {
26 | expect(HtmlUtils.getAttributeValue(createElement(''), 'foo')).toBe('');
27 | });
28 |
29 | test('"null"', () => {
30 | expect(HtmlUtils.getAttributeValue(createElement(''), 'foo')).toBe('null');
31 | });
32 |
33 | test('numeric', () => {
34 | expect(HtmlUtils.getAttributeValue(createElement(''), 'foo')).toBe('42');
35 | });
36 | });
37 |
38 | describe('getNormalizedAttributeValue', () => {
39 |
40 | test('indendation', () => {
41 | expect(HtmlUtils.getNormalizedAttributeValue(createElement(''), 'foo', {
45 | preserveIndentation: true,
46 | trimWhiteSpace: true,
47 | replaceNewLines: false
48 | })).toBe(
49 | ' Foo\n' +
50 | ' Bar'
51 | );
52 | });
53 |
54 | test('new lines', () => {
55 | expect(HtmlUtils.getNormalizedAttributeValue(createElement(''), 'foo', {
59 | preserveIndentation: false,
60 | trimWhiteSpace: true,
61 | replaceNewLines: ' '
62 | })).toBe('Foo Bar');
63 | });
64 | });
65 |
66 | describe('getElementContent', () => {
67 |
68 | function getContent(source: string): string {
69 | return HtmlUtils.getElementContent(createElement(source), {
70 | preserveIndentation: true,
71 | trimWhiteSpace: true,
72 | replaceNewLines: false
73 | });
74 | }
75 |
76 | test('single line', () => {
77 | expect(getContent('Foo Bar
')).toBe('Foo Bar');
78 | });
79 |
80 | test('nested element', () => {
81 | expect(getContent('Foo Bar
')).toBe('Foo Bar');
82 | });
83 |
84 | test('indentation', () => {
85 | expect(getContent(
86 | '\n' +
87 | ' Foo\n' +
88 | ' Bar\n' +
89 | '
'
90 | )).toBe(
91 | ' Foo\n' +
92 | ' Bar'
93 | );
94 | });
95 |
96 | describe('un-escaping', () => {
97 |
98 | test('&', () => {
99 | expect(getContent('Foo & Bar
')).toBe('Foo & Bar');
100 | });
101 |
102 | test('<', () => {
103 | expect(getContent('Foo < Bar
')).toBe('Foo < Bar');
104 | });
105 |
106 | test('>', () => {
107 | expect(getContent('Foo > Bar
')).toBe('Foo > Bar');
108 | });
109 | });
110 | });
111 | });
112 |
--------------------------------------------------------------------------------
/tests/indent.ts:
--------------------------------------------------------------------------------
1 | export function trimIndent(literals: string): string {
2 | const lines = literals.split('\n');
3 | const commonIndent = lines.reduce((minIndent: number | undefined, line: string) => {
4 | const match = line.match(/^(\s*)\S+/);
5 | if (match !== null) {
6 | if (minIndent === undefined) {
7 | return match[1].length;
8 | } else {
9 | return Math.min(match[1].length, minIndent);
10 | }
11 | }
12 | return minIndent;
13 | }, undefined);
14 | return lines.map(line => line.slice(commonIndent)).join('\n');
15 | }
16 |
--------------------------------------------------------------------------------
/tests/js/extractors/factories/htmlTemplate.test.ts:
--------------------------------------------------------------------------------
1 | import { htmlTemplateExtractor } from '../../../../src/js/extractors/factories/htmlTemplate'
2 | import { HtmlExtractors } from '../../../../src/html/extractors';
3 | import { JsParser } from '../../../../src/js/parser';
4 | import { HtmlParser } from '../../../../src/html/parser';
5 | import { CatalogBuilder, IMessage } from '../../../../src/builder';
6 |
7 | describe('JS: HTML template extractor', () => {
8 | describe('calling html parser ', () => {
9 | let builder: CatalogBuilder;
10 | let messages: IMessage[]
11 | let jsParser: JsParser;
12 | let htmlParser: HtmlParser;
13 |
14 | beforeEach(() => {
15 | messages = [];
16 |
17 | builder = {
18 | addMessage: jest.fn((message: IMessage) => {
19 | messages.push(message);
20 | })
21 | };
22 |
23 | htmlParser = new HtmlParser(builder, [
24 | HtmlExtractors.elementContent('translate')
25 | ]);
26 |
27 | jsParser = new JsParser(builder, [
28 | htmlTemplateExtractor(htmlParser)
29 | ]);
30 | });
31 |
32 | test('single line (regular string)', () => {
33 | jsParser.parseString('let itBe = " test
"');
34 | expect(messages).toEqual([
35 | {
36 | text: 'test'
37 | }
38 | ])
39 | });
40 |
41 | test('single line (template string)', () => {
42 | jsParser.parseString('let itBe = " test
"');
43 | expect(messages).toEqual([
44 | {
45 | text: 'test'
46 | }
47 | ])
48 | });
49 |
50 | test('with lineNumberStart option (regular string)', () => {
51 | jsParser.parseString(
52 | 'let itBe = " test
"',
53 | 'test',
54 | { lineNumberStart: 10 }
55 | );
56 |
57 | expect(messages).toEqual([
58 | {
59 | text: 'test',
60 | references: ['test:10'],
61 | },
62 | ])
63 | })
64 |
65 | test('with lineNumberStart option (template string)', () => {
66 | jsParser.parseString(
67 | 'let itBe = " test
"',
68 | 'test',
69 | { lineNumberStart: 10 }
70 | );
71 |
72 | expect(messages).toEqual([
73 | {
74 | text: 'test',
75 | references: ['test:10'],
76 | },
77 | ])
78 | })
79 |
80 | test('HTML inside a template literal with the correct line numbers', () => {
81 | jsParser.parseString(`
82 |
83 |
84 |
85 |
86 |
87 |
88 | let tuce = \`
89 |
90 |
First level
91 |
92 |
Second level
93 |
Third level
94 |
95 |
\`
96 | `, 'test')
97 |
98 | expect(messages).toEqual([
99 | {
100 | text: 'First level',
101 | references: ['test:10'],
102 | },
103 | {
104 | text: 'Second level',
105 | references: ['test:12'],
106 | },
107 | {
108 | text: 'Third level',
109 | references: ['test:13'],
110 | },
111 | ])
112 | })
113 |
114 | test('HTML inside a template literal with correct line numbers and with lineNumberStart', () => {
115 | jsParser.parseString(`
116 |
117 |
118 |
119 |
120 |
121 |
122 | let tuce = \`
123 |
124 |
First level
125 |
126 |
Second level
127 |
Third level
128 |
129 |
\`
130 | `, 'test', { lineNumberStart: 10 })
131 |
132 | expect(messages).toEqual([
133 | {
134 | text: 'First level',
135 | references: ['test:19'],
136 | },
137 | {
138 | text: 'Second level',
139 | references: ['test:21'],
140 | },
141 | {
142 | text: 'Third level',
143 | references: ['test:22'],
144 | },
145 | ])
146 | })
147 | })
148 | })
--------------------------------------------------------------------------------
/tests/js/parser.test.ts:
--------------------------------------------------------------------------------
1 | import * as ts from 'typescript';
2 |
3 | import { JsParser, IJsParseOptions } from '../../src/js/parser';
4 | import { registerCommonParserTests } from '../parser.common';
5 | import { UnicodeSamples } from '../fixtures/unicode';
6 | import { CatalogBuilder } from '../../src/builder';
7 |
8 | describe('JsParser', () => {
9 |
10 | registerCommonParserTests(JsParser);
11 |
12 | describe('line number', () => {
13 |
14 | let parser: JsParser;
15 | let builderMock: CatalogBuilder;
16 |
17 | beforeEach(() => {
18 | builderMock = {
19 | addMessage: jest.fn()
20 | };
21 | parser = new JsParser(builderMock, [(node: ts.Node, sourceFile: ts.SourceFile, addMessage) => {
22 | if (node.kind === ts.SyntaxKind.StringLiteral) {
23 | addMessage({
24 | text: (node).text
25 | });
26 | }
27 | }]);
28 | });
29 |
30 | test('first line', () => {
31 | parser.parseString(`'Foo'`, 'foo.html');
32 |
33 | expect(builderMock.addMessage).toHaveBeenCalledWith({
34 | text: 'Foo',
35 | references: ['foo.html:1']
36 | });
37 | });
38 |
39 | test('third line', () => {
40 | parser.parseString(`\n\n'Foo'`, 'foo.html');
41 |
42 | expect(builderMock.addMessage).toHaveBeenCalledWith({
43 | text: 'Foo',
44 | references: ['foo.html:3']
45 | });
46 | });
47 |
48 | test('with offset', () => {
49 | parser.parseString(`'Foo'`, 'foo.html', {
50 | lineNumberStart: 10
51 | });
52 |
53 | expect(builderMock.addMessage).toHaveBeenCalledWith({
54 | text: 'Foo',
55 | references: ['foo.html:10']
56 | });
57 | });
58 | });
59 |
60 | test('transform source function', () => {
61 | let parser = new JsParser({}, [jest.fn()]);
62 | let parseFunctionMock = (parser).parse = jest.fn(() => []);
63 |
64 | const fileName = 'foo.ts';
65 | const parseOptions: IJsParseOptions = {
66 | transformSource: source => source.toUpperCase()
67 | };
68 |
69 | parser.parseString('foo', fileName, parseOptions);
70 | expect(parseFunctionMock).toHaveBeenCalledWith('FOO', fileName, parseOptions);
71 | });
72 |
73 | describe('unicode', () => {
74 |
75 | function check(text: string): void {
76 | let parser = new JsParser(new CatalogBuilder(), [(node: ts.Node) => {
77 | if (node.kind === ts.SyntaxKind.StringLiteral) {
78 | expect((node as ts.StringLiteral).text).toEqual(text);
79 | }
80 | }]);
81 |
82 | parser.parseString(`"${text}"`);
83 | }
84 |
85 | test('danish', () => {
86 | check(UnicodeSamples.danish);
87 | });
88 |
89 | test('german', () => {
90 | check(UnicodeSamples.german);
91 | });
92 |
93 | test('greek', () => {
94 | check(UnicodeSamples.greek);
95 | });
96 |
97 | test('english', () => {
98 | check(UnicodeSamples.english);
99 | });
100 |
101 | test('spanish', () => {
102 | check(UnicodeSamples.spanish);
103 | });
104 |
105 | test('french', () => {
106 | check(UnicodeSamples.french);
107 | });
108 |
109 | test('irish gaelic', () => {
110 | check(UnicodeSamples.irishGaelic);
111 | });
112 |
113 | test('hungarian', () => {
114 | check(UnicodeSamples.hungarian);
115 | });
116 |
117 | test('icelandic', () => {
118 | check(UnicodeSamples.icelandic);
119 | });
120 |
121 | test('japanese', () => {
122 | check(UnicodeSamples.japanese);
123 | });
124 |
125 | test('hebrew', () => {
126 | check(UnicodeSamples.hebrew);
127 | });
128 |
129 | test('polish', () => {
130 | check(UnicodeSamples.polish);
131 | });
132 |
133 | test('russian', () => {
134 | check(UnicodeSamples.russian);
135 | });
136 |
137 | test('thai', () => {
138 | check(UnicodeSamples.thai);
139 | });
140 |
141 | test('turkish', () => {
142 | check(UnicodeSamples.turkish);
143 | });
144 | });
145 | });
146 |
--------------------------------------------------------------------------------
/tests/js/utils.test.ts:
--------------------------------------------------------------------------------
1 | import * as ts from 'typescript';
2 | import { JsUtils } from '../../src/js/utils';
3 |
4 | describe('JS: Utils', () => {
5 |
6 | describe('segmentsMatchPropertyExpression', () => {
7 |
8 | function getExpression(source: string): ts.PropertyAccessExpression {
9 | let sourceFile = ts.createSourceFile('foo.ts', source, ts.ScriptTarget.Latest, true);
10 |
11 | return sourceFile.getChildAt(0).getChildAt(0).getChildAt(0);
12 | }
13 |
14 | test('standard case', () => {
15 | let segments = ['foo', 'bar'];
16 |
17 | expect(JsUtils.segmentsMatchPropertyExpression(segments, getExpression('foo.bar'))).toBe(true);
18 | expect(JsUtils.segmentsMatchPropertyExpression(segments, getExpression('bar.foo'))).toBe(false);
19 | expect(JsUtils.segmentsMatchPropertyExpression(segments, getExpression('this.foo.bar'))).toBe(false);
20 | expect(JsUtils.segmentsMatchPropertyExpression(segments, getExpression('baz.foo.bar'))).toBe(false);
21 | });
22 |
23 | test('long path', () => {
24 | let segments = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten'];
25 |
26 | expect(JsUtils.segmentsMatchPropertyExpression(segments, getExpression('one.two.three.four.five.six.seven.eight.nine.ten'))).toBe(true);
27 | expect(JsUtils.segmentsMatchPropertyExpression(segments, getExpression('two.three.four.five.six.seven.eight.nine.ten'))).toBe(false);
28 | expect(JsUtils.segmentsMatchPropertyExpression(segments, getExpression('one.two.three.four.five.six.seven.eight.nine'))).toBe(false);
29 | });
30 |
31 | test('this keyword', () => {
32 | let segments = ['this', 'foo', 'bar'];
33 |
34 | expect(JsUtils.segmentsMatchPropertyExpression(segments, getExpression('this.foo.bar'))).toBe(true);
35 | expect(JsUtils.segmentsMatchPropertyExpression(segments, getExpression('foo.bar'))).toBe(false);
36 | });
37 |
38 | test('optional this keyword', () => {
39 | let segments = ['[this]', 'foo', 'bar'];
40 |
41 | expect(JsUtils.segmentsMatchPropertyExpression(segments, getExpression('this.foo.bar'))).toBe(true);
42 | expect(JsUtils.segmentsMatchPropertyExpression(segments, getExpression('foo.bar'))).toBe(true);
43 | });
44 |
45 | test('this keyword not at first position', () => {
46 | let segments = ['foo', 'this', 'bar'];
47 |
48 | expect(JsUtils.segmentsMatchPropertyExpression(segments, getExpression('foo.this.bar'))).toBe(true);
49 | expect(JsUtils.segmentsMatchPropertyExpression(segments, getExpression('this.foo.this.bar'))).toBe(false);
50 | expect(JsUtils.segmentsMatchPropertyExpression(segments, getExpression('this.foo.bar'))).toBe(false);
51 | expect(JsUtils.segmentsMatchPropertyExpression(segments, getExpression('this.bar'))).toBe(false);
52 | });
53 |
54 | test('case sensitivity', () => {
55 | expect(JsUtils.segmentsMatchPropertyExpression(['this', 'foo'], getExpression('this.FOO'))).toBe(false);
56 | expect(JsUtils.segmentsMatchPropertyExpression(['this', 'foo'], getExpression('THIS.foo'))).toBe(false);
57 | expect(JsUtils.segmentsMatchPropertyExpression(['THIS', 'foo'], getExpression('this.foo'))).toBe(false);
58 | expect(JsUtils.segmentsMatchPropertyExpression(['THIS', 'foo'], getExpression('THIS.foo'))).toBe(true);
59 | });
60 | });
61 | });
62 |
--------------------------------------------------------------------------------
/tests/parser.common.ts:
--------------------------------------------------------------------------------
1 | import { Parser, IAddMessageCallback } from '../src/parser';
2 | import { CatalogBuilder, IMessage } from '../src/builder';
3 | import { IGettextExtractorStats } from '../src/extractor';
4 |
5 | export function registerCommonParserTests(parserClass: any): void {
6 | let parser: Parser,
7 | builder: CatalogBuilder,
8 | messages: IMessage[];
9 |
10 | beforeEach(() => {
11 | messages = [];
12 |
13 | builder = {
14 | stats: {},
15 | addMessage: jest.fn((message: IMessage) => {
16 | messages.push(message);
17 | })
18 | };
19 | });
20 |
21 | test('one extractor', () => {
22 | let extractor = jest.fn();
23 | parser = new parserClass(builder, [extractor]);
24 | parser.parseString('');
25 | expect(extractor).toHaveBeenCalled();
26 | });
27 |
28 | test('multiple extractors', () => {
29 | let extractor1 = jest.fn(),
30 | extractor2 = jest.fn();
31 | parser = new parserClass(builder, [extractor1, extractor2]);
32 | parser.parseString('');
33 | expect(extractor1).toHaveBeenCalled();
34 | expect(extractor2).toHaveBeenCalled();
35 | });
36 |
37 | test('extractor added later', () => {
38 | let extractor = jest.fn();
39 | parser = new parserClass(builder);
40 | parser.addExtractor(extractor);
41 | parser.parseString('');
42 | expect(extractor).toHaveBeenCalled();
43 | });
44 |
45 | test('second extractor added later', () => {
46 | let extractor1 = jest.fn(),
47 | extractor2 = jest.fn();
48 | parser = new parserClass(builder, [extractor1]);
49 | parser.parseString('');
50 | expect(extractor1).toHaveBeenCalled();
51 | expect(extractor2).not.toHaveBeenCalled();
52 |
53 | extractor1.mockClear();
54 | extractor2.mockClear();
55 |
56 | parser.addExtractor(extractor2);
57 | parser.parseString('');
58 | expect(extractor1).toHaveBeenCalled();
59 | expect(extractor2).toHaveBeenCalled();
60 | });
61 |
62 | test('addMessage call', () => {
63 | let extractor = jest.fn().mockImplementationOnce((node: any, file: any, addMessage: IAddMessageCallback) => {
64 | addMessage({
65 | text: 'Foo'
66 | });
67 | });
68 |
69 | parser = new parserClass(builder, [extractor]);
70 | parser.parseString('');
71 |
72 | expect(messages).toEqual([
73 | {
74 | text: 'Foo'
75 | }
76 | ]);
77 | });
78 |
79 | test('fluid api', () => {
80 | let extractor1 = jest.fn(),
81 | extractor2 = jest.fn();
82 |
83 | parser = new parserClass(builder, [extractor1]);
84 |
85 | expect(parser.parseString('')).toBe(parser);
86 | expect(parser.parseFilesGlob('tests/fixtures/*.ts')).toBe(parser);
87 | expect(parser.parseFile('tests/fixtures/empty.ts')).toBe(parser);
88 | expect(parser.addExtractor(extractor2)).toBe(parser);
89 | });
90 |
91 | describe('stats', () => {
92 |
93 | let stats: IGettextExtractorStats;
94 |
95 | beforeEach(() => {
96 | stats = {
97 | numberOfMessages: 0,
98 | numberOfPluralMessages: 0,
99 | numberOfMessageUsages: 0,
100 | numberOfContexts: 0,
101 | numberOfParsedFiles: 0,
102 | numberOfParsedFilesWithMessages: 0
103 | };
104 | });
105 |
106 | test('no files with messages', () => {
107 | let extractor = jest.fn();
108 |
109 | parser = new parserClass(builder, [extractor], stats);
110 |
111 | parser.parseString('');
112 | parser.parseString('');
113 | parser.parseString('');
114 |
115 | expect(stats.numberOfParsedFiles).toBe(3);
116 | expect(stats.numberOfParsedFilesWithMessages).toBe(0);
117 | });
118 |
119 | test('some files with messages', () => {
120 | let extractor = jest.fn().mockImplementationOnce((node: any, file: any, addMessage: IAddMessageCallback) => {
121 | addMessage({
122 | text: 'Foo'
123 | });
124 | });
125 |
126 | parser = new parserClass(builder, [extractor], stats);
127 |
128 | parser.parseString('');
129 | parser.parseString('');
130 | parser.parseString('');
131 |
132 | expect(stats.numberOfParsedFiles).toBe(3);
133 | expect(stats.numberOfParsedFilesWithMessages).toBe(1);
134 | });
135 |
136 | test('all files with messages', () => {
137 | let extractor = jest.fn().mockImplementation((node: any, file: any, addMessage: IAddMessageCallback) => {
138 | addMessage({
139 | text: 'Foo'
140 | });
141 | });
142 |
143 | parser = new parserClass(builder, [extractor], stats);
144 |
145 | parser.parseString('');
146 | parser.parseString('');
147 | parser.parseString('');
148 |
149 | expect(stats.numberOfParsedFiles).toBe(3);
150 | expect(stats.numberOfParsedFilesWithMessages).toBe(3);
151 | });
152 | });
153 |
154 | test('parsing without extractors', () => {
155 | const ERROR_MESSAGE = `Missing extractor functions. Provide them when creating the parser or dynamically add extractors using 'addExtractor()'`;
156 |
157 | parser = new parserClass(builder);
158 |
159 | expect(() => {
160 | parser.parseString('');
161 | }).toThrowError(ERROR_MESSAGE);
162 |
163 | expect(() => {
164 | parser.parseFile('tests/fixtures/empty.ts');
165 | }).toThrowError(ERROR_MESSAGE);
166 |
167 | expect(() => {
168 | parser.parseFilesGlob('tests/fixtures/*.ts');
169 | }).toThrowError(ERROR_MESSAGE);
170 | });
171 |
172 | describe('argument validation', () => {
173 |
174 | beforeEach(() => {
175 | parser = new parserClass(builder, [jest.fn()]);
176 | });
177 |
178 | describe('parseFile', () => {
179 |
180 | test('fileName: (none)', () => {
181 | expect(() => {
182 | (parser.parseFile)();
183 | }).toThrowError(`Missing argument 'fileName'`);
184 | });
185 |
186 | test('fileName: null', () => {
187 | expect(() => {
188 | (parser.parseFile)(null);
189 | }).toThrowError(`Argument 'fileName' must be a non-empty string`);
190 | });
191 |
192 | test('fileName: wrong type', () => {
193 | expect(() => {
194 | (parser.parseFile)(42);
195 | }).toThrowError(`Argument 'fileName' must be a non-empty string`);
196 | });
197 |
198 | test('options: wrong type', () => {
199 | expect(() => {
200 | (parser.parseFile)('foo.ts', 'bar');
201 | }).toThrowError(`Argument 'options' must be an object`);
202 | });
203 |
204 | test('options.lineNumberStart: wrong type', () => {
205 | expect(() => {
206 | (parser.parseFile)('foo.ts', {
207 | lineNumberStart: 'bar'
208 | });
209 | }).toThrowError(`Property 'options.lineNumberStart' must be a number`);
210 | });
211 |
212 | test('options.transformSource: wrong type', () => {
213 | expect(() => {
214 | (parser.parseFile)('foo.ts', {
215 | transformSource: 42
216 | });
217 | }).toThrowError(`Property 'options.transformSource' must be a function`);
218 | });
219 | });
220 |
221 | describe('parseFilesGlob', () => {
222 |
223 | test('pattern: (none)', () => {
224 | expect(() => {
225 | (parser.parseFilesGlob)();
226 | }).toThrowError(`Missing argument 'pattern'`);
227 | });
228 |
229 | test('pattern: null', () => {
230 | expect(() => {
231 | (parser.parseFilesGlob)(null);
232 | }).toThrowError(`Argument 'pattern' must be a non-empty string`);
233 | });
234 |
235 | test('pattern: wrong type', () => {
236 | expect(() => {
237 | (parser.parseFilesGlob)(42);
238 | }).toThrowError(`Argument 'pattern' must be a non-empty string`);
239 | });
240 |
241 | test('globOptions: wrong type', () => {
242 | expect(() => {
243 | (parser.parseFilesGlob)('*.ts;', 'foo');
244 | }).toThrowError(`Argument 'globOptions' must be an object`);
245 | });
246 |
247 | test('options: wrong type', () => {
248 | expect(() => {
249 | (parser.parseFilesGlob)('*.ts;', {}, 'foo');
250 | }).toThrowError(`Argument 'options' must be an object`);
251 | });
252 |
253 | test('options.lineNumberStart: wrong type', () => {
254 | expect(() => {
255 | (parser.parseFilesGlob)('*.ts;', {}, {
256 | lineNumberStart: 'foo'
257 | });
258 | }).toThrowError(`Property 'options.lineNumberStart' must be a number`);
259 | });
260 |
261 | test('options.transformSource: wrong type', () => {
262 | expect(() => {
263 | (parser.parseFile)('foo.ts', {
264 | transformSource: 42
265 | });
266 | }).toThrowError(`Property 'options.transformSource' must be a function`);
267 | });
268 | });
269 |
270 | describe('parseString', () => {
271 |
272 | test('source: (none)', () => {
273 | expect(() => {
274 | (parser.parseString)();
275 | }).toThrowError(`Missing argument 'source'`);
276 | });
277 |
278 | test('source: null', () => {
279 | expect(() => {
280 | (parser.parseString)(null);
281 | }).toThrowError(`Argument 'source' must be a string`);
282 | });
283 |
284 | test('source: wrong type', () => {
285 | expect(() => {
286 | (parser.parseString)(42);
287 | }).toThrowError(`Argument 'source' must be a string`);
288 | });
289 |
290 | test('fileName: (none)', () => {
291 | expect(() => {
292 | (parser.parseString)('let foo = 42;');
293 | }).not.toThrow();
294 | });
295 |
296 | test('fileName: wrong type', () => {
297 | expect(() => {
298 | (parser.parseString)('let foo = 42;', 42);
299 | }).toThrowError(`Argument 'fileName' must be a non-empty string`);
300 | });
301 |
302 | test('options: wrong type', () => {
303 | expect(() => {
304 | (parser.parseString)('let foo = 42;', 'foo.ts', 'bar');
305 | }).toThrowError(`Argument 'options' must be an object`);
306 | });
307 |
308 | test('options.lineNumberStart: wrong type', () => {
309 | expect(() => {
310 | (parser.parseString)('let foo = 42;', 'foo.ts', {
311 | lineNumberStart: 'bar'
312 | });
313 | }).toThrowError(`Property 'options.lineNumberStart' must be a number`);
314 | });
315 |
316 | test('options.transformSource: wrong type', () => {
317 | expect(() => {
318 | (parser.parseFile)('foo.ts', {
319 | transformSource: 42
320 | });
321 | }).toThrowError(`Property 'options.transformSource' must be a function`);
322 | });
323 | });
324 |
325 | describe('addExtractor', () => {
326 |
327 | test('extractor: (none)', () => {
328 | expect(() => {
329 | (parser.addExtractor)();
330 | }).toThrowError(`Missing argument 'extractor'`);
331 | });
332 |
333 | test('extractor: null', () => {
334 | expect(() => {
335 | (parser.addExtractor)(null);
336 | }).toThrowError(`Invalid extractor function provided. 'null' is not a function`);
337 | });
338 |
339 | test('extractor: wrong type', () => {
340 | expect(() => {
341 | (parser.addExtractor)(42);
342 | }).toThrowError(`Invalid extractor function provided. '42' is not a function`);
343 | });
344 | });
345 | });
346 | }
347 |
--------------------------------------------------------------------------------
/tests/parser.test.ts:
--------------------------------------------------------------------------------
1 | import { IAddMessageCallback, Parser } from '../src/parser';
2 | import { IMessage } from '../src/builder';
3 |
4 | export const foo = 'bar';
5 |
6 | describe('Abstract Parser', () => {
7 |
8 | describe('createAddMessageCallback', () => {
9 |
10 | let lineNumber: number,
11 | messages: IMessage[],
12 | callback: IAddMessageCallback;
13 |
14 | beforeEach(() => {
15 | messages = [];
16 | callback = Parser.createAddMessageCallback(messages, 'foo.ts', () => lineNumber);
17 | });
18 |
19 | test('single call', () => {
20 | lineNumber = 16;
21 |
22 | callback({
23 | text: 'Foo'
24 | });
25 |
26 | expect(messages).toEqual([
27 | {
28 | text: 'Foo',
29 | references: ['foo.ts:16']
30 | }
31 | ]);
32 | });
33 |
34 | test('multiple calls', () => {
35 | lineNumber = 16;
36 |
37 | callback({
38 | text: 'Foo'
39 | });
40 |
41 | lineNumber = 17;
42 |
43 | callback({
44 | text: 'Bar'
45 | });
46 |
47 | expect(messages).toEqual([
48 | {
49 | text: 'Foo',
50 | references: ['foo.ts:16']
51 | },
52 | {
53 | text: 'Bar',
54 | references: ['foo.ts:17']
55 | }
56 | ]);
57 | });
58 |
59 | test('plural', () => {
60 | lineNumber = 16;
61 |
62 | callback({
63 | text: 'Foo',
64 | textPlural: 'Foos'
65 | });
66 |
67 | expect(messages).toEqual([
68 | {
69 | text: 'Foo',
70 | textPlural: 'Foos',
71 | references: ['foo.ts:16']
72 | }
73 | ]);
74 | });
75 |
76 | test('context', () => {
77 | lineNumber = 16;
78 |
79 | callback({
80 | text: 'Foo',
81 | context: 'Context'
82 | });
83 |
84 | expect(messages).toEqual([
85 | {
86 | text: 'Foo',
87 | context: 'Context',
88 | references: ['foo.ts:16']
89 | }
90 | ]);
91 | });
92 |
93 | test('custom line number', () => {
94 | lineNumber = 16;
95 |
96 | callback({
97 | text: 'Foo',
98 | lineNumber: 100
99 | });
100 |
101 | expect(messages).toEqual([
102 | {
103 | text: 'Foo',
104 | references: ['foo.ts:100']
105 | }
106 | ]);
107 | });
108 |
109 | test('custom file name', () => {
110 | lineNumber = 16;
111 |
112 | callback({
113 | text: 'Foo',
114 | fileName: 'bar.ts'
115 | });
116 |
117 | expect(messages).toEqual([
118 | {
119 | text: 'Foo',
120 | references: ['bar.ts:16']
121 | }
122 | ]);
123 | });
124 |
125 | test('custom file name and line number', () => {
126 | lineNumber = 16;
127 |
128 | callback({
129 | text: 'Foo',
130 | fileName: 'bar.ts',
131 | lineNumber: 100
132 | });
133 |
134 | expect(messages).toEqual([
135 | {
136 | text: 'Foo',
137 | references: ['bar.ts:100']
138 | }
139 | ]);
140 | });
141 |
142 | test('string literal file name', () => {
143 | lineNumber = 16;
144 | callback = Parser.createAddMessageCallback(messages, Parser.STRING_LITERAL_FILENAME, () => lineNumber);
145 |
146 | callback({
147 | text: 'Foo'
148 | });
149 |
150 | expect(messages).toEqual([
151 | {
152 | text: 'Foo'
153 | }
154 | ]);
155 | });
156 |
157 | test('comments', () => {
158 | lineNumber = 16;
159 |
160 | callback({
161 | text: 'Foo',
162 | comments: ['Comment 1', 'Comment 2']
163 | });
164 |
165 | expect(messages).toEqual([
166 | {
167 | text: 'Foo',
168 | references: ['foo.ts:16'],
169 | comments: ['Comment 1', 'Comment 2']
170 | }
171 | ]);
172 | });
173 | });
174 | });
175 |
--------------------------------------------------------------------------------
/tests/utils/content.test.ts:
--------------------------------------------------------------------------------
1 | import { IContentOptions, normalizeContent } from '../../src/utils/content';
2 |
3 | describe('Content Utils', () => {
4 |
5 | describe('normalizeContent', () => {
6 |
7 | type Scenario = 'default' | 'noTrim' | 'preserveIndentation' | 'noTrimPreserveIndentation' | 'replaceNewlinesCRLF' | 'replaceNewlinesCRLFPreserveIndentation';
8 |
9 | const scenarios: {[scenario in Scenario]: IContentOptions} = {
10 | default: {
11 | preserveIndentation: false,
12 | replaceNewLines: false,
13 | trimWhiteSpace: true
14 | },
15 | noTrim: {
16 | preserveIndentation: false,
17 | replaceNewLines: false,
18 | trimWhiteSpace: false
19 | },
20 | preserveIndentation: {
21 | preserveIndentation: true,
22 | replaceNewLines: false,
23 | trimWhiteSpace: true
24 | },
25 | noTrimPreserveIndentation: {
26 | preserveIndentation: true,
27 | replaceNewLines: false,
28 | trimWhiteSpace: false
29 | },
30 | replaceNewlinesCRLF: {
31 | preserveIndentation: false,
32 | replaceNewLines: '\r\n',
33 | trimWhiteSpace: true
34 | },
35 | replaceNewlinesCRLFPreserveIndentation: {
36 | preserveIndentation: true,
37 | replaceNewLines: '\r\n',
38 | trimWhiteSpace: true
39 | }
40 | };
41 |
42 | function registerNormalizeContentTests(newLine: string, whitespace: string): void {
43 |
44 | function testCase(summary: string, source: string, expectedResults: { [scenario in Scenario]: string }): void {
45 | const whitespacePlaceholder = / {4}/g;
46 | source = source.replace(/\n/g, newLine).replace(whitespacePlaceholder, whitespace);
47 |
48 | describe(summary, () => {
49 | for (let scenario of Object.keys(scenarios)) {
50 | test(scenario, () => {
51 | expect(normalizeContent(source, (scenarios as any)[scenario]))
52 | .toBe((expectedResults as any)[scenario].replace(whitespacePlaceholder, whitespace));
53 | });
54 | }
55 | });
56 | }
57 |
58 | testCase('single line',
59 | 'Foo Bar',
60 | {
61 | default:
62 | 'Foo Bar',
63 | noTrim:
64 | 'Foo Bar',
65 | preserveIndentation:
66 | 'Foo Bar',
67 | noTrimPreserveIndentation:
68 | 'Foo Bar',
69 | replaceNewlinesCRLF:
70 | 'Foo Bar',
71 | replaceNewlinesCRLFPreserveIndentation:
72 | 'Foo Bar'
73 | }
74 | );
75 |
76 | testCase('leading and trailing newline',
77 | '\n' +
78 | 'Foo Bar\n',
79 | {
80 | default:
81 | 'Foo Bar',
82 | noTrim:
83 | '\n' +
84 | 'Foo Bar\n',
85 | preserveIndentation:
86 | 'Foo Bar',
87 | noTrimPreserveIndentation:
88 | '\n' +
89 | 'Foo Bar\n',
90 | replaceNewlinesCRLF:
91 | 'Foo Bar',
92 | replaceNewlinesCRLFPreserveIndentation:
93 | 'Foo Bar'
94 | }
95 | );
96 |
97 | testCase('leading and trailing newline with indentation',
98 | '\n' +
99 | ' Foo Bar\n',
100 | {
101 | default:
102 | 'Foo Bar',
103 | noTrim:
104 | '\n' +
105 | 'Foo Bar\n',
106 | preserveIndentation:
107 | ' Foo Bar',
108 | noTrimPreserveIndentation:
109 | '\n' +
110 | ' Foo Bar\n',
111 | replaceNewlinesCRLF:
112 | 'Foo Bar',
113 | replaceNewlinesCRLFPreserveIndentation:
114 | ' Foo Bar'
115 | }
116 | );
117 |
118 | testCase('indented leading newline',
119 | ' \n' +
120 | ' Foo Bar\n',
121 | {
122 | default:
123 | 'Foo Bar',
124 | noTrim:
125 | '\n' +
126 | 'Foo Bar\n',
127 | preserveIndentation:
128 | ' Foo Bar',
129 | noTrimPreserveIndentation:
130 | ' \n' +
131 | ' Foo Bar\n',
132 | replaceNewlinesCRLF:
133 | 'Foo Bar',
134 | replaceNewlinesCRLFPreserveIndentation:
135 | ' Foo Bar'
136 | }
137 | );
138 |
139 | testCase('multiple leading newlines',
140 | '\n' +
141 | '\n' +
142 | 'Foo Bar',
143 | {
144 | default:
145 | 'Foo Bar',
146 | noTrim:
147 | '\n' +
148 | '\n' +
149 | 'Foo Bar',
150 | preserveIndentation:
151 | 'Foo Bar',
152 | noTrimPreserveIndentation:
153 | '\n' +
154 | '\n' +
155 | 'Foo Bar',
156 | replaceNewlinesCRLF:
157 | 'Foo Bar',
158 | replaceNewlinesCRLFPreserveIndentation:
159 | 'Foo Bar'
160 | }
161 | );
162 |
163 | testCase('multiple trailing newlines',
164 | 'Foo Bar\n' +
165 | '\n',
166 | {
167 | default:
168 | 'Foo Bar',
169 | noTrim:
170 | 'Foo Bar\n' +
171 | '\n',
172 | preserveIndentation:
173 | 'Foo Bar',
174 | noTrimPreserveIndentation:
175 | 'Foo Bar\n' +
176 | '\n',
177 | replaceNewlinesCRLF:
178 | 'Foo Bar',
179 | replaceNewlinesCRLFPreserveIndentation:
180 | 'Foo Bar'
181 | }
182 | );
183 |
184 | testCase('multiple content lines',
185 | '\n' +
186 | 'Foo\n' +
187 | 'Bar\n',
188 | {
189 | default:
190 | 'Foo\n' +
191 | 'Bar',
192 | noTrim:
193 | '\n' +
194 | 'Foo\n' +
195 | 'Bar\n',
196 | preserveIndentation:
197 | 'Foo\n' +
198 | 'Bar',
199 | noTrimPreserveIndentation:
200 | '\n' +
201 | 'Foo\n' +
202 | 'Bar\n',
203 | replaceNewlinesCRLF:
204 | 'Foo\r\n' +
205 | 'Bar',
206 | replaceNewlinesCRLFPreserveIndentation:
207 | 'Foo\r\n' +
208 | 'Bar'
209 | }
210 | );
211 |
212 | testCase('multiple content lines with indentation',
213 | '\n' +
214 | ' Foo\n' +
215 | ' Bar\n',
216 | {
217 | default:
218 | 'Foo\n' +
219 | 'Bar',
220 | noTrim:
221 | '\n' +
222 | 'Foo\n' +
223 | 'Bar\n',
224 | preserveIndentation:
225 | ' Foo\n' +
226 | ' Bar',
227 | noTrimPreserveIndentation:
228 | '\n' +
229 | ' Foo\n' +
230 | ' Bar\n',
231 | replaceNewlinesCRLF:
232 | 'Foo\r\n' +
233 | 'Bar',
234 | replaceNewlinesCRLFPreserveIndentation:
235 | ' Foo\r\n' +
236 | ' Bar'
237 | }
238 | );
239 | }
240 |
241 | describe('LF & spaces', () => {
242 |
243 | registerNormalizeContentTests('\n', ' ');
244 | });
245 |
246 | describe('LF & tabs', () => {
247 |
248 | registerNormalizeContentTests('\n', '\t');
249 | });
250 |
251 | describe('CRLF & spaces', () => {
252 |
253 | registerNormalizeContentTests('\r\n', ' ');
254 | });
255 |
256 | describe('CRLF & tabs', () => {
257 |
258 | registerNormalizeContentTests('\r\n', '\t');
259 | });
260 | });
261 | });
262 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "dist/",
4 | "target": "ES6",
5 | "module": "commonjs",
6 | "declaration": true,
7 | "newLine": "LF",
8 | "strict": true
9 | },
10 | "files": [
11 | "src/index.ts"
12 | ],
13 | "exclude": [
14 | "node_modules"
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "class-name": true,
4 | "curly": true,
5 | "comment-format": [
6 | true,
7 | "check-space"
8 | ],
9 | "eofline": true,
10 | "indent": [
11 | true,
12 | "spaces"
13 | ],
14 | "interface-name": true,
15 | "member-access": true,
16 | "member-ordering": [
17 | true,
18 | "variables-before-functions",
19 | "static-before-instance",
20 | "public-before-private"
21 | ],
22 | "no-eval": true,
23 | "no-shadowed-variable": true,
24 | "no-internal-module": true,
25 | "no-trailing-whitespace": true,
26 | "no-unsafe-finally": true,
27 | "no-var-keyword": true,
28 | "one-line": [
29 | true,
30 | "check-catch",
31 | "check-finally",
32 | "check-else",
33 | "check-open-brace",
34 | "check-whitespace"
35 | ],
36 | "quotemark": [
37 | true,
38 | "single",
39 | "jsx-double"
40 | ],
41 | "radix": true,
42 | "semicolon": [
43 | true,
44 | "always"
45 | ],
46 | "trailing-comma": [
47 | true,
48 | {
49 | "multiline": "never",
50 | "singleline": "never"
51 | }
52 | ],
53 | "triple-equals": true,
54 | "typedef": [
55 | true,
56 | "call-signature",
57 | "parameter",
58 | "property-declaration",
59 | "member-variable-declaration"
60 | ],
61 | "typedef-whitespace": [
62 | true,
63 | {
64 | "call-signature": "nospace",
65 | "index-signature": "nospace",
66 | "parameter": "nospace",
67 | "property-declaration": "nospace",
68 | "variable-declaration": "nospace"
69 | },
70 | {
71 | "call-signature": "space",
72 | "index-signature": "space",
73 | "parameter": "space",
74 | "property-declaration": "space",
75 | "variable-declaration": "space"
76 | }
77 | ],
78 | "variable-name": [
79 | true,
80 | "check-format",
81 | "ban-keywords"
82 | ],
83 | "whitespace": [
84 | true,
85 | "check-branch",
86 | "check-decl",
87 | "check-operator",
88 | "check-module",
89 | "check-separator",
90 | "check-type",
91 | "check-preblock"
92 | ]
93 | }
94 | }
95 |
--------------------------------------------------------------------------------