UNPKG

10.9 kBJavaScriptView Raw
1const I = require('immutable');
2const {expect, assert} = require('chai');
3
4import {extractMessages as extract, InputError} from '../src/extract';
5
6
7describe('extraction', function() {
8 describe('of strings', function() {
9 it('extracts a string', function() {
10 let messages = extract('i18n("foo")');
11
12 expect(messages).to.eql(['foo']);
13 });
14
15 it('extracts multiple strings', function() {
16 let messages = extract(`
17 let foo = i18n("foo foo foo");
18 let bar = i18n("foo, bar, foo");
19 let baz = \`\${i18n('this is silly')}\`;
20 `);
21
22 expect(messages).to.eql([
23 'foo foo foo',
24 'foo, bar, foo',
25 'this is silly'
26 ]);
27 });
28 });
29
30 describe('of jsx', function() {
31 it('extracts simple strings', function() {
32 let messages = extract(`
33 React.createClass({
34 render() {
35 return <div>
36 <I18N>O, hai.</I18N>
37 <I18N>You look nice today!</I18N>
38 </div>;
39 }
40 })
41 `);
42
43 expect(messages).to.eql([
44 'O, hai.',
45 'You look nice today!'
46 ]);
47 });
48
49 it('extracts strings with expressions', function() {
50 let messages = extract(`
51 React.createClass({
52 render() {
53 let name = this.props.name;
54 return <div>
55 <I18N>O, hai, {name}.</I18N>
56 <I18N>You look nice today, {this.props.subject}!</I18N>
57 </div>;
58 }
59 })
60 `);
61
62 expect(messages).to.eql([
63 'O, hai, {name}.',
64 'You look nice today, {this.props.subject}!'
65 ]);
66 });
67
68 it('extracts strings with nested components', function() {
69 let messages = extract(`
70 React.createClass({
71 render() {
72 let name = this.props.name;
73 return <div>
74 <I18N>O, hai, <span>{name}</span>.</I18N>
75 <I18N>You look <em>nice</em> today, <strong>{this.props.subject}</strong>!</I18N>
76 </div>;
77 }
78 })
79 `);
80
81 expect(messages).to.eql([
82 'O, hai, <span>{name}</span>.',
83 'You look <em>nice</em> today, <strong>{this.props.subject}</strong>!'
84 ]);
85 });
86
87 it('extracts strings with nested components with attributes', function() {
88 let messages = extract(`
89 React.createClass({
90 render() {
91 let name = this.props.name;
92 return <div>
93 <I18N>O, hai, <span title="boop">{name}</span>.</I18N>
94 <I18N>You look <a href="#nice">nice</a> today, <strong>{this.props.subject}</strong>!</I18N>
95 </div>;
96 }
97 })
98 `);
99
100 expect(messages).to.eql([
101 'O, hai, <span title="boop">{name}</span>.',
102 'You look <a href="#nice">nice</a> today, <strong>{this.props.subject}</strong>!'
103 ]);
104 });
105
106 it('extracts strings with nested components with i18n-id attributes', function() {
107 let messages = extract(`
108 React.createClass({
109 render() {
110 let name = this.props.name;
111 return <div>
112 <I18N><span i18n-id="step-2" className="step-text">Step 2: </span>Add your organization to Idealist</I18N>
113 </div>;
114 }
115 })
116 `);
117
118 expect(messages).to.eql([
119 '<span:step-2>Step 2: </span:step-2>Add your organization to Idealist'
120 ]);
121 });
122
123 it('extracts strings with nested components with namespaced i18n-id', function() {
124 let messages = extract(`
125 React.createClass({
126 render() {
127 let name = this.props.name;
128 return <div>
129 <I18N><span:step-2 className="step-text">Step 2: </span:step-2>Add your organization to Idealist</I18N>
130 </div>;
131 }
132 })
133 `);
134
135 expect(messages).to.eql([
136 '<span:step-2>Step 2: </span:step-2>Add your organization to Idealist'
137 ]);
138 });
139
140 it('extracts strings with nested components with no children', function() {
141 let messages = extract(`
142 React.createClass({
143 render() {
144 let name = this.props.name;
145 return <div>
146 <I18N>Line, <br title="boop"/>Break.</I18N>
147 <I18N>React <Components/>, am I right?</I18N>
148 </div>;
149 }
150 })
151 `);
152
153 expect(messages).to.eql([
154 'Line, <br title="boop" />Break.',
155 'React <Components />, am I right?'
156 ]);
157 });
158
159 it('does not assume an i18n-id is present when there are unsafe attributes', function() {
160 let messages = extract(`
161 <li><I18N><span i18n-id="stat" className="stat"><ReactIntl.FormattedNumber value={dailyVisitors}/></span>daily visitors</I18N></li>
162 `);
163
164 expect(messages).to.eql([
165 '<span:stat><ReactIntl.FormattedNumber /></span:stat>daily visitors'
166 ]);
167 });
168
169 it('deals correctly with whitespace', function() {
170 let messages = extract(`<p id="are-we-eligible" className="in-form-link">
171 <I18N>
172 <a href="/info/Help/Organizations#Eligibility">Are we eligible?</a>
173 </I18N>
174 </p>`);
175
176 expect(messages).to.eql([
177 '<a href="/info/Help/Organizations#Eligibility">Are we eligible?</a>'
178 ]);
179 });
180
181 describe('of various strings', function() {
182 var extractions = {
183 'Hello': [],
184 '<I18N>Hello</I18N>': ['Hello'],
185 'i18n("world")': ['world'],
186 '<I18N><a href="foo">tag with only safe attributes</a></I18N>': ['<a href="foo">tag with only safe attributes</a>'],
187 '<I18N><a:link href="foo" target="_blank">tag with unsafe attributes</a:link></I18N>': ['<a:link href="foo">tag with unsafe attributes</a:link>'],
188 '<I18N><a href="foo" target="_blank" i18n-id="link">tag with unsafe attributes</a></I18N>': ['<a:link href="foo">tag with unsafe attributes</a:link>'],
189 '<I18N><SelfClosing i18n-id="foo" attr="attr" /></I18N>': ['<SelfClosing:foo />'],
190 '<I18N><SelfClosing /></I18N>': ['<SelfClosing />'],
191 '<I18N><SelfClosing:a /><SelfClosing:b /></I18N>': ['<SelfClosing:a /><SelfClosing:b />'],
192 '<I18N><Member.Name /></I18N>': ['<Member.Name />'],
193 '<I18N><a><b><i>Deeply nested</i> nested <i>nested</i> nested</b> tags</a></I18N>': ['<a><b><i>Deeply nested</i> nested <i>nested</i> nested</b> tags</a>'],
194 '<I18N>Cat: {hat}</I18N>': ['Cat: {hat}'],
195 '<I18N>And now {a.member.expression}</I18N>': ['And now {a.member.expression}'],
196 'var {nested, ...rested} = i18n("hatters"); <I18N>Cat: {nested}</I18N>': ['hatters', 'Cat: {nested}'],
197 '<p><I18N>1: {same.name.different.message}</I18N> <I18N>2: {same.name.different.message}</I18N></p>': ['1: {same.name.different.message}', '2: {same.name.different.message}'],
198 '<I18N><Pluralize:count on={count}><Match when="zero">You have no items</Match><Match when="one">You have one item</Match><Match when="other">You have {count} items</Match></Pluralize:count></I18N>': [
199 '<Pluralize:count><Match when="zero">You have no items</Match><Match when="one">You have one item</Match><Match when="other">You have {count} items</Match></Pluralize:count>'],
200 };
201
202 it('extracts expected strings', function() {
203 Object.keys(extractions).forEach(input => {
204 try { extract(input); } catch(e) { console.error(e); }
205 assert(I.is(I.fromJS(extractions[input]), I.fromJS(extract(input))),
206 `
207 Incorrect extraction for input
208 ${input}
209 Expected
210 ${extractions[input]}
211 but got
212 ${extract(input)}
213 `);
214 });
215 });
216 });
217 });
218
219 describe('errors and warnings', function() {
220 it('throws an error when an element has sanitized attributes but no i18n-id', function() {
221 expect(() => extract('<I18N>O, hai, <span className="boop">{name}</span>.</I18N>')).to.throw(InputError);
222 });
223
224 it('does not require i18n-id on unique components', function() {
225 expect(() => extract('<I18N>O, hai, <Component beep="boop">{name}</Component>.</I18N>')).to.not.throw(InputError);
226 });
227
228 it('requires i18n-id on duplicated components', function() {
229 expect(() => extract('<I18N>O, hai, <C beep="boop">{name}</C>, <C beep="boöp">{game}</C>.</I18N>')).to.throw(InputError);
230 });
231
232 it('warns on unextractable messages', function() {
233 var shouldNotBeExtractable = [
234 '<I18N>Nested <I18N>message markers.</I18N></I18N>',
235 'i18n("Not" + "just a string" + "literal")',
236 'i18n()',
237 'i18n("Too many", "arguments")',
238 '<I18N><a target="_blank">Unsafe attributes but no id.</a></I18N>',
239 '<I18N><Doubled/>two of the same Component type without ids<Doubled/></I18N>',
240 '<I18N><Doubled:doubled/>two of the same Component type with the same ids<Doubled:doubled/></I18N>',
241 '<I18N>{"string literal"}</I18N>',
242 '<I18N>{arbitrary.expression()}</I18N>',
243 '<I18N>{("non"+"simple").memberExpression}</I18N>',
244 '<I18N>{computed["memberExpression"]}</I18N>'
245 ];
246
247 shouldNotBeExtractable.forEach(msg => {
248 expect(() => extract(msg)).to.throw;
249 });
250 });
251 });
252});