Fix formatLocaleNumericRoundedByCurrency
[civicrm-core.git] / tests / phpunit / CRM / Utils / JSTest.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
10 */
11
12 /**
13 * Tests for linking to resource files
14 * @group headless
15 */
16 class CRM_Utils_JSTest extends CiviUnitTestCase {
17
18 /**
19 * @return array
20 */
21 public function translateExamples() {
22 $cases = [];
23 $cases[] = [
24 '',
25 [],
26 ];
27 // missing ts
28 $cases[] = [
29 'alert("Hello world")',
30 [],
31 ];
32 // basic function call
33 $cases[] = [
34 'alert(ts("Hello world"));',
35 ['Hello world'],
36 ];
37 // with arg
38 $cases[] = [
39 'alert(ts("Hello world", {1: "whiz"}));',
40 ['Hello world'],
41 ];
42 // not really ts()
43 $cases[] = [
44 'alert(clients("Hello world"));',
45 [],
46 ];
47 // not really ts()
48 $cases[] = [
49 'alert(clients("Hello world", {1: "whiz"}));',
50 [],
51 ];
52 // with arg
53 $cases[] = [
54 "\n" .
55 "public function whits() {\n" .
56 " for (a in b) {\n" .
57 " mitts(\"wallaby\", function(zoo) {\n" .
58 " alert(zoo + ts(\"Hello\"))\n" .
59 " });\n" .
60 " }\n" .
61 "}\n",
62 ['Hello'],
63 ];
64 // duplicate
65 $cases[] = [
66 'alert(ts("Hello world") + "-" + ts("Hello world"));',
67 ['Hello world'],
68 ];
69 // two strings, addition
70 $cases[] = [
71 'alert(ts("Hello world") + "-" + ts("How do you do?"));',
72 ['Hello world', 'How do you do?'],
73 ];
74 // two strings, separate calls
75 $cases[] = [
76 'alert(ts("Hello world");\nalert(ts("How do you do?"));',
77 ['Hello world', 'How do you do?'],
78 ];
79 $cases[] = [
80 'alert(ts(\'Single quoted\'));',
81 ['Single quoted'],
82 ];
83 // unclear string
84 $cases[] = [
85 'alert(ts(message));',
86 [],
87 ];
88 // ts() within a string
89 $cases[] = [
90 'alert(ts("Does the ts(\'example\') notation work?"));',
91 ['Does the ts(\'example\') notation work?'],
92 ];
93 return $cases;
94 }
95
96 /**
97 * @param string $jsCode
98 * @param array $expectedStrings
99 * @dataProvider translateExamples
100 */
101 public function testParseStrings($jsCode, $expectedStrings) {
102 $actualStrings = CRM_Utils_JS::parseStrings($jsCode);
103 sort($expectedStrings);
104 sort($actualStrings);
105 $this->assertEquals($expectedStrings, $actualStrings);
106 }
107
108 public function dedupeClosureExamples() {
109 // Each example string here is named for its body, eg the body of $a calls "a()".
110 $a = "(function (angular, $, _) {\n 'use strict';\n a();\n})(angular, CRM.$, CRM._);";
111 $b = "(function(angular,$,_){\n \"use strict\";\n b();\n})(angular,CRM.$,CRM._);";
112 $c = "(function( angular, $,_) {\nc();\n})(angular,CRM.$, CRM._);";
113 $d = "(function (angular, $, _, whiz) {\nd();\n})(angular, CRM.$, CRM._, CRM.whizbang);";
114 $m = "alert('i is the trickster (function( angular, $,_) {\nm();\n})(angular,CRM.$, CRM._);)'";
115 // Note: $d has a fundamentally different closure.
116
117 // Each example string here is a deduped combination of others,
118 // eg "$ab" is the deduping of $a+$b.
119 $ab = "(function (angular, $, _) {\n 'use strict';\n a();\n b();\n})(angular,CRM.$,CRM._);";
120 $ba = "(function(angular,$,_){\n \"use strict\";\n b();\n a();\n})(angular, CRM.$, CRM._);";
121 $abc = "(function (angular, $, _) {\n 'use strict';\n a();\n b();\nc();\n})(angular,CRM.$, CRM._);";
122 $cb = "(function( angular, $,_) {\nc();\n b();\n})(angular,CRM.$,CRM._);";
123
124 $cases = [];
125 $cases[] = [[$a], "$a"];
126 $cases[] = [[$b], "$b"];
127 $cases[] = [[$c], "$c"];
128 $cases[] = [[$d], "$d"];
129 $cases[] = [[$m], "$m"];
130 $cases[] = [[$a, $b], "$ab"];
131 $cases[] = [[$b, $a], "$ba"];
132 $cases[] = [[$a, $m, $b], "$a$m$b"];
133 $cases[] = [[$a, $d], "$a$d"];
134 $cases[] = [[$a, $d, $b], "$a$d$b"];
135 $cases[] = [[$a, $b, $c], "$abc"];
136 $cases[] = [[$a, $b, $d, $c, $b], "$ab$d$cb"];
137 return $cases;
138 }
139
140 /**
141 * @param array $scripts
142 * @param string $expectedOutput
143 * @dataProvider dedupeClosureExamples
144 */
145 public function testDedupeClosure($scripts, $expectedOutput) {
146 $actualOutput = CRM_Utils_JS::dedupeClosures(
147 $scripts,
148 ['angular', '$', '_'],
149 ['angular', 'CRM.$', 'CRM._']
150 );
151 $this->assertEquals($expectedOutput, implode("", $actualOutput));
152 }
153
154 public function stripCommentsExamples() {
155 $cases = [];
156 $cases[] = [
157 "a();\n//# sourceMappingURL=../foo/bar/baz.js\nb();",
158 "a();\nb();",
159 ];
160 $cases[] = [
161 "// foo\na();",
162 "a();",
163 ];
164 $cases[] = [
165 "// foo\n //\na();",
166 "a();",
167 ];
168 $cases[] = [
169 "b();\n // foo",
170 "b();\n",
171 ];
172 $cases[] = [
173 "/// foo\na();\n\t \t//bar\nb();\n// whiz",
174 "a();\nb();\n",
175 ];
176 $cases[] = [
177 "alert('//# sourceMappingURL=../foo/bar/baz.js');\n//zoop\na();",
178 "alert('//# sourceMappingURL=../foo/bar/baz.js');\na();",
179 ];
180 return $cases;
181 }
182
183 /**
184 * @param string $input
185 * @param string $expectedOutput
186 * @dataProvider stripCommentsExamples
187 */
188 public function testStripComments($input, $expectedOutput) {
189 $this->assertEquals($expectedOutput, CRM_Utils_JS::stripComments($input));
190 }
191
192 public static function decodeExamples() {
193 return [
194 ['{a: \'Apple\', \'b\': "Banana", c: [1, 2, 3]}', ['a' => 'Apple', 'b' => 'Banana', 'c' => [1, 2, 3]]],
195 ['true', TRUE],
196 [' ', NULL],
197 ['false', FALSE],
198 ['null', NULL],
199 ['"true"', 'true'],
200 ['0.5', 0.5],
201 [" {}", []],
202 ["[]", []],
203 ["{ }", []],
204 [" [ ]", []],
205 [" [ 2 ]", [2]],
206 [
207 '{a: "parse error no closing bracket"',
208 NULL,
209 ],
210 [
211 '{a: ["foo", \'bar\'], "b": {a: [\'foo\', "bar"], b: {\'a\': ["foo", "bar"], b: {}}}}',
212 ['a' => ['foo', 'bar'], 'b' => ['a' => ['foo', 'bar'], 'b' => ['a' => ['foo', 'bar'], 'b' => []]]],
213 ],
214 [
215 ' [{a: {aa: true}, b: [false, null, {x: 1, y: 2, z: 3}] , "c": -1}, ["fee", "fie", \'foe\']]',
216 [['a' => ['aa' => TRUE], 'b' => [FALSE, NULL, ['x' => 1, 'y' => 2, 'z' => 3]], "c" => -1], ["fee", "fie", "foe"]],
217 ],
218 ["'Hi\&'", 'Hi&'],
219 ['"Hello " + alert("XSS") + " sucker"', NULL],
220 ["'Hello ' + alert('XSS') + ' sucker'", NULL],
221 ];
222 }
223
224 /**
225 * @param string $input
226 * @param string $expectedOutput
227 * @dataProvider decodeExamples
228 */
229 public function testDecode($input, $expectedOutput) {
230 $this->assertEquals($expectedOutput, CRM_Utils_JS::decode($input));
231 }
232
233 public static function encodeExamples() {
234 return [
235 [
236 ['a' => 'Apple', 'b' => 'Banana', 'c' => [0, -2, 3.15]],
237 "{a: 'Apple', b: 'Banana', c: [0, -2, 3.15]}",
238 ],
239 [
240 ['a' => ['foo', 'bar'], 'b' => ["'a'" => ['foo/bar&', 'bar(foo)'], 'b' => ['a' => ["fo\\'oo", '"bar"'], 'b' => []]]],
241 "{a: ['foo', 'bar'], b: {\"'a'\": ['foo/bar&', 'bar(foo)'], b: {a: ['fo\\\\\\'oo', '\"bar\"'], b: {}}}}",
242 ],
243 [TRUE, 'true'],
244 [' ', "' '"],
245 [FALSE, 'false'],
246 [NULL, 'null'],
247 ['true', "'true'"],
248 ['"false"', "'\"false\"'"],
249 ['0.5', "'0.5'"],
250 [0.5, '0.5'],
251 [[], "{}"],
252 ];
253 }
254
255 /**
256 * @param string $input
257 * @param string $expectedOutput
258 * @dataProvider encodeExamples
259 */
260 public function testEncode($input, $expectedOutput) {
261 $result = CRM_Utils_JS::encode($input);
262 $this->assertEquals($expectedOutput, $result);
263 $this->assertEquals($input, CRM_Utils_JS::decode($result));
264 }
265
266 /**
267 * @return array
268 */
269 public static function objectExamples() {
270 return [
271 [
272 '{a: \'Apple\', \'b\': "Banana", "c ": [1,2,3]}',
273 ['a' => "'Apple'", 'b' => '"Banana"', 'c ' => '[1,2,3]'],
274 '{a: \'Apple\', b: "Banana", \'c \': [1,2,3]}',
275 ],
276 [
277 " {}",
278 [],
279 "{}",
280 ],
281 [
282 " [ ] ",
283 [],
284 "{}",
285 ],
286 [
287 " {'fn' : function (foo, bar, baz) { return \"One, two, three\"; }, esc: /[1-9]\\\\/.test('5\\\\') , number : 55.5/2 } ",
288 ['fn' => 'function (foo, bar, baz) { return "One, two, three"; }', 'esc' => "/[1-9]\\\\/.test('5\\\\')", 'number' => '55.5/2'],
289 "{fn: function (foo, bar, baz) { return \"One, two, three\"; }, esc: /[1-9]\\\\/.test('5\\\\'), number: 55.5/2}",
290 ],
291 [
292 "{ string :
293 'this, has(some : weird, \\'stuff [{}!' ,
294 expr: sum(1, 2, 3) / 2 + 1, ' notes ' : [Do, re mi],
295 }",
296 ['string' => "'this, has(some : weird, \\'stuff [{}!'", 'expr' => 'sum(1, 2, 3) / 2 + 1', ' notes ' => "[Do, re mi]"],
297 "{string: 'this, has(some : weird, \\'stuff [{}!', expr: sum(1, 2, 3) / 2 + 1, ' notes ': [Do, re mi]}",
298 ],
299 [
300 '{status: /^http:\/\/civicrm\.com/.test(url) ? \'good\' : \'bad\' , \'foo\&\': getFoo("Some \"quoted\" thing"), "ba\'[(r": function() {return "bar"}}',
301 ['status' => '/^http:\/\/civicrm\.com/.test(url) ? \'good\' : \'bad\'', 'foo&' => 'getFoo("Some \"quoted\" thing")', "ba'[(r" => 'function() {return "bar"}'],
302 '{status: /^http:\/\/civicrm\.com/.test(url) ? \'good\' : \'bad\', "foo&": getFoo("Some \"quoted\" thing"), "ba\'[(r": function() {return "bar"}}',
303 ],
304 [
305 '{"some\"key": typeof foo === \'number\' ? true : false , "O\'Really?": ",((,", \'A"quote"\': 1 + 1 , "\\\\&\\/" : 0}',
306 ['some"key' => 'typeof foo === \'number\' ? true : false', "O'Really?" => '",((,"', 'A"quote"' => '1 + 1', '\\&/' => '0'],
307 '{\'some"key\': typeof foo === \'number\' ? true : false, "O\'Really?": ",((,", \'A"quote"\': 1 + 1, "\\\\&/": 0}',
308 ],
309 [
310 '[foo ? 1 : 2 , 3 , function() {return 1 + 1;}, /^http:\/\/civicrm\.com/.test(url) ? \'good\' : \'bad\' , 3.14 ]',
311 ['foo ? 1 : 2', '3', 'function() {return 1 + 1;}', '/^http:\/\/civicrm\.com/.test(url) ? \'good\' : \'bad\'', '3.14'],
312 '[foo ? 1 : 2, 3, function() {return 1 + 1;}, /^http:\/\/civicrm\.com/.test(url) ? \'good\' : \'bad\', 3.14]',
313 ],
314 ];
315 }
316
317 /**
318 * Test converting a js string to a php array and back again.
319 *
320 * @param string $input
321 * @param string $expectedPHP
322 * @param $expectedJS
323 * @dataProvider objectExamples
324 */
325 public function testObjectToAndFromString($input, $expectedPHP, $expectedJS) {
326 $objectProps = CRM_Utils_JS::getRawProps($input);
327 $this->assertEquals($expectedPHP, $objectProps);
328 $reformattedJS = CRM_Utils_JS::writeObject($objectProps);
329 $this->assertEquals($expectedJS, $reformattedJS);
330 $this->assertEquals($expectedPHP, CRM_Utils_JS::getRawProps($reformattedJS));
331 }
332
333 }