Merge pull request #14638 from civicrm/5.15
[civicrm-core.git] / tests / phpunit / CRM / Utils / JSTest.php
CommitLineData
6a488035
TO
1<?php
2/*
3+--------------------------------------------------------------------+
2fe49090 4| CiviCRM version 5 |
6a488035 5+--------------------------------------------------------------------+
6b83d5bd 6| Copyright CiviCRM LLC (c) 2004-2019 |
6a488035
TO
7+--------------------------------------------------------------------+
8| This file is a part of CiviCRM. |
9| |
10| CiviCRM is free software; you can copy, modify, and distribute it |
11| under the terms of the GNU Affero General Public License |
12| Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13| |
14| CiviCRM is distributed in the hope that it will be useful, but |
15| WITHOUT ANY WARRANTY; without even the implied warranty of |
16| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17| See the GNU Affero General Public License for more details. |
18| |
19| You should have received a copy of the GNU Affero General Public |
20| License and the CiviCRM Licensing Exception along |
21| with this program; if not, contact CiviCRM LLC |
22| at info[AT]civicrm[DOT]org. If you have questions about the |
23| GNU Affero General Public License or the licensing of CiviCRM, |
24| see the CiviCRM license FAQ at http://civicrm.org/licensing |
25+--------------------------------------------------------------------+
e70a7fc0 26 */
6a488035 27
6a488035
TO
28/**
29 * Tests for linking to resource files
acb109b7 30 * @group headless
6a488035
TO
31 */
32class CRM_Utils_JSTest extends CiviUnitTestCase {
39b959db 33
4cbe18b8
EM
34 /**
35 * @return array
36 */
00be9182 37 public function translateExamples() {
6a488035
TO
38 $cases = array();
39 $cases[] = array(
40 '',
41 array(),
42 );
39b959db
SL
43 // missing ts
44 $cases[] = array(
6a488035
TO
45 'alert("Hello world")',
46 array(),
47 );
39b959db
SL
48 // basic function call
49 $cases[] = array(
6a488035
TO
50 'alert(ts("Hello world"));',
51 array('Hello world'),
52 );
39b959db
SL
53 // with arg
54 $cases[] = array(
6a488035
TO
55 'alert(ts("Hello world", {1: "whiz"}));',
56 array('Hello world'),
57 );
39b959db
SL
58 // not really ts()
59 $cases[] = array(
6a488035
TO
60 'alert(clients("Hello world"));',
61 array(),
62 );
39b959db
SL
63 // not really ts()
64 $cases[] = array(
6a488035
TO
65 'alert(clients("Hello world", {1: "whiz"}));',
66 array(),
67 );
39b959db
SL
68 // with arg
69 $cases[] = array(
0c7a8599
TO
70 "\n" .
71 "public function whits() {\n" .
72 " for (a in b) {\n" .
73 " mitts(\"wallaby\", function(zoo) {\n" .
74 " alert(zoo + ts(\"Hello\"))\n" .
75 " });\n" .
76 " }\n" .
77 "}\n",
6a488035
TO
78 array('Hello'),
79 );
39b959db
SL
80 // duplicate
81 $cases[] = array(
6a488035
TO
82 'alert(ts("Hello world") + "-" + ts("Hello world"));',
83 array('Hello world'),
84 );
39b959db
SL
85 // two strings, addition
86 $cases[] = array(
6a488035
TO
87 'alert(ts("Hello world") + "-" + ts("How do you do?"));',
88 array('Hello world', 'How do you do?'),
89 );
39b959db
SL
90 // two strings, separate calls
91 $cases[] = array(
6a488035
TO
92 'alert(ts("Hello world");\nalert(ts("How do you do?"));',
93 array('Hello world', 'How do you do?'),
94 );
95 $cases[] = array(
96 'alert(ts(\'Single quoted\'));',
97 array('Single quoted'),
98 );
39b959db
SL
99 // unclear string
100 $cases[] = array(
6a488035
TO
101 'alert(ts(message));',
102 array(),
103 );
39b959db
SL
104 // ts() within a string
105 $cases[] = array(
6a488035
TO
106 'alert(ts("Does the ts(\'example\') notation work?"));',
107 array('Does the ts(\'example\') notation work?'),
108 );
109 return $cases;
110 }
111
112 /**
113 * @param string $jsCode
114 * @param array $expectedStrings
115 * @dataProvider translateExamples
116 */
00be9182 117 public function testParseStrings($jsCode, $expectedStrings) {
6a488035
TO
118 $actualStrings = CRM_Utils_JS::parseStrings($jsCode);
119 sort($expectedStrings);
120 sort($actualStrings);
121 $this->assertEquals($expectedStrings, $actualStrings);
122 }
96025800 123
ad295ca9
TO
124 public function dedupeClosureExamples() {
125 // Each example string here is named for its body, eg the body of $a calls "a()".
126 $a = "(function (angular, $, _) {\na();\n})(angular, CRM.$, CRM._);";
127 $b = "(function(angular,$,_){\nb();\n})(angular,CRM.$,CRM._);";
128 $c = "(function( angular, $,_) {\nc();\n})(angular,CRM.$, CRM._);";
129 $d = "(function (angular, $, _, whiz) {\nd();\n})(angular, CRM.$, CRM._, CRM.whizbang);";
130 $m = "alert('i is the trickster (function( angular, $,_) {\nm();\n})(angular,CRM.$, CRM._);)'";
131 // Note: $d has a fundamentally different closure.
132
133 // Each example string here is a deduped combination of others,
134 // eg "$ab" is the deduping of $a+$b.
135 $ab = "(function (angular, $, _) {\na();\n\nb();\n})(angular,CRM.$,CRM._);";
136 $abc = "(function (angular, $, _) {\na();\n\nb();\n\nc();\n})(angular,CRM.$, CRM._);";
137 $cb = "(function( angular, $,_) {\nc();\n\nb();\n})(angular,CRM.$,CRM._);";
138
139 $cases = array();
140 $cases[] = array(array($a), "$a");
141 $cases[] = array(array($b), "$b");
142 $cases[] = array(array($c), "$c");
143 $cases[] = array(array($d), "$d");
144 $cases[] = array(array($m), "$m");
145 $cases[] = array(array($a, $b), "$ab");
146 $cases[] = array(array($a, $m, $b), "$a$m$b");
147 $cases[] = array(array($a, $d), "$a$d");
148 $cases[] = array(array($a, $d, $b), "$a$d$b");
149 $cases[] = array(array($a, $b, $c), "$abc");
150 $cases[] = array(array($a, $b, $d, $c, $b), "$ab$d$cb");
151 return $cases;
152 }
153
154 /**
155 * @param array $scripts
156 * @param string $expectedOutput
157 * @dataProvider dedupeClosureExamples
158 */
159 public function testDedupeClosure($scripts, $expectedOutput) {
160 $actualOutput = CRM_Utils_JS::dedupeClosures(
161 $scripts,
162 array('angular', '$', '_'),
163 array('angular', 'CRM.$', 'CRM._')
164 );
165 $this->assertEquals($expectedOutput, implode("", $actualOutput));
166 }
b047e061
TO
167
168 public function stripCommentsExamples() {
169 $cases = array();
170 $cases[] = array(
171 "a();\n//# sourceMappingURL=../foo/bar/baz.js\nb();",
172 "a();\n\nb();",
173 );
174 $cases[] = array(
175 "// foo\na();",
176 "\na();",
177 );
178 $cases[] = array(
179 "b();\n // foo",
180 "b();\n",
181 );
182 $cases[] = array(
183 "/// foo\na();\n\t \t//bar\nb();\n// whiz",
184 "\na();\n\nb();\n",
185 );
186 $cases[] = array(
187 "alert('//# sourceMappingURL=../foo/bar/baz.js');\n//zoop\na();",
188 "alert('//# sourceMappingURL=../foo/bar/baz.js');\n\na();",
189 );
190 return $cases;
191 }
192
193 /**
194 * @param string $input
195 * @param string $expectedOutput
196 * @dataProvider stripCommentsExamples
197 */
198 public function testStripComments($input, $expectedOutput) {
199 $this->assertEquals($expectedOutput, CRM_Utils_JS::stripComments($input));
200 }
201
a49c5ad6
CW
202 public static function decodeExamples() {
203 return [
204 ['{a: \'Apple\', \'b\': "Banana", c: [1, 2, 3]}', ['a' => 'Apple', 'b' => 'Banana', 'c' => [1, 2, 3]]],
205 ['true', TRUE],
206 ['false', FALSE],
207 ['null', NULL],
9511ca30 208 ['"true"', 'true'],
a49c5ad6 209 ['0.5', 0.5],
9511ca30 210 [" {}", []],
a49c5ad6 211 ["[]", []],
9511ca30
CW
212 ["{ }", []],
213 [" [ ]", []],
214 [" [ 2 ]", [2]],
215 [
216 '{a: ["foo", \'bar\'], "b": {a: [\'foo\', "bar"], b: {\'a\': ["foo", "bar"], b: {}}}}',
217 ['a' => ['foo', 'bar'], 'b' => ['a' => ['foo', 'bar'], 'b' => ['a' => ['foo', 'bar'], 'b' => []]]],
218 ],
219 [
220 ' [{a: {aa: true}, b: [false, null, {x: 1, y: 2, z: 3}] , "c": -1}, ["fee", "fie", \'foe\']]',
221 [['a' => ['aa' => TRUE], 'b' => [FALSE, NULL, ['x' => 1, 'y' => 2, 'z' => 3]], "c" => -1], ["fee", "fie", "foe"]],
222 ],
a49c5ad6
CW
223 ];
224 }
225
226 /**
227 * @param string $input
228 * @param string $expectedOutput
229 * @dataProvider decodeExamples
230 */
231 public function testDecode($input, $expectedOutput) {
232 $this->assertEquals($expectedOutput, CRM_Utils_JS::decode($input));
233 }
234
9511ca30
CW
235 /**
236 * @return array
237 */
3203414a
CW
238 public static function objectExamples() {
239 return [
240 [
241 '{a: \'Apple\', \'b\': "Banana", "c ": [1,2,3]}',
242 ['a' => "'Apple'", 'b' => '"Banana"', 'c ' => '[1,2,3]'],
9511ca30 243 '{a: \'Apple\', b: "Banana", \'c \': [1,2,3]}',
3203414a
CW
244 ],
245 [
9511ca30
CW
246 " {}",
247 [],
3203414a 248 "{}",
9511ca30
CW
249 ],
250 [
251 " [ ] ",
3203414a 252 [],
9511ca30
CW
253 "{}",
254 ],
255 [
256 " {'fn' : function (foo, bar, baz) { return \"One, two, three\"; }, esc: /[1-9]\\\\/.test('5\\\\') , number : 55.5/2 } ",
257 ['fn' => 'function (foo, bar, baz) { return "One, two, three"; }', 'esc' => "/[1-9]\\\\/.test('5\\\\')", 'number' => '55.5/2'],
258 "{fn: function (foo, bar, baz) { return \"One, two, three\"; }, esc: /[1-9]\\\\/.test('5\\\\'), number: 55.5/2}",
259 ],
260 [
261 "{ string :
262 'this, has(some : weird, \\'stuff [{}!' ,
263 expr: sum(1, 2, 3) / 2 + 1, ' notes ' : [Do, re mi],
264 }",
265 ['string' => "'this, has(some : weird, \\'stuff [{}!'", 'expr' => 'sum(1, 2, 3) / 2 + 1', ' notes ' => "[Do, re mi]"],
266 "{string: 'this, has(some : weird, \\'stuff [{}!', expr: sum(1, 2, 3) / 2 + 1, ' notes ': [Do, re mi]}",
3203414a
CW
267 ],
268 [
9511ca30
CW
269 '{status: /^http:\/\/civicrm\.com/.test(url) ? \'good\' : \'bad\' , \'foo\&\': getFoo("Some \"quoted\" thing"), "ba\'[(r": function() {return "bar"}}',
270 ['status' => '/^http:\/\/civicrm\.com/.test(url) ? \'good\' : \'bad\'', 'foo&' => 'getFoo("Some \"quoted\" thing")', "ba'[(r" => 'function() {return "bar"}'],
271 '{status: /^http:\/\/civicrm\.com/.test(url) ? \'good\' : \'bad\', "foo&": getFoo("Some \"quoted\" thing"), "ba\'[(r": function() {return "bar"}}',
3203414a
CW
272 ],
273 [
9511ca30
CW
274 '{"some\"key": typeof foo === \'number\' ? true : false , "O\'Really?": ",((,", \'A"quote"\': 1 + 1 , "\\\\\\&\\/" : 0}',
275 ['some"key' => 'typeof foo === \'number\' ? true : false', "O'Really?" => '",((,"', 'A"quote"' => '1 + 1', '\\&/' => '0'],
276 '{\'some"key\': typeof foo === \'number\' ? true : false, "O\'Really?": ",((,", \'A"quote"\': 1 + 1, "\\\\&/": 0}',
3203414a
CW
277 ],
278 [
9511ca30
CW
279 '[foo ? 1 : 2 , 3 , function() {return 1 + 1;}, /^http:\/\/civicrm\.com/.test(url) ? \'good\' : \'bad\' , 3.14 ]',
280 ['foo ? 1 : 2', '3', 'function() {return 1 + 1;}', '/^http:\/\/civicrm\.com/.test(url) ? \'good\' : \'bad\'', '3.14'],
281 '[foo ? 1 : 2, 3, function() {return 1 + 1;}, /^http:\/\/civicrm\.com/.test(url) ? \'good\' : \'bad\', 3.14]',
3203414a
CW
282 ],
283 ];
284 }
285
286 /**
9511ca30
CW
287 * Test converting a js string to a php array and back again.
288 *
3203414a 289 * @param string $input
9511ca30
CW
290 * @param string $expectedPHP
291 * @param $expectedJS
3203414a
CW
292 * @dataProvider objectExamples
293 */
9511ca30
CW
294 public function testObjectToAndFromString($input, $expectedPHP, $expectedJS) {
295 $objectProps = CRM_Utils_JS::getRawProps($input);
296 $this->assertEquals($expectedPHP, $objectProps);
297 $reformattedJS = CRM_Utils_JS::writeObject($objectProps);
298 $this->assertEquals($expectedJS, $reformattedJS);
299 $this->assertEquals($expectedPHP, CRM_Utils_JS::getRawProps($reformattedJS));
3203414a
CW
300 }
301
6a488035 302}