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