assertEquals($expectedStrings, $actualStrings); } public function dedupeClosureExamples() { // Each example string here is named for its body, eg the body of $a calls "a()". $a = "(function (angular, $, _) {\na();\n})(angular, CRM.$, CRM._);"; $b = "(function(angular,$,_){\nb();\n})(angular,CRM.$,CRM._);"; $c = "(function( angular, $,_) {\nc();\n})(angular,CRM.$, CRM._);"; $d = "(function (angular, $, _, whiz) {\nd();\n})(angular, CRM.$, CRM._, CRM.whizbang);"; $m = "alert('i is the trickster (function( angular, $,_) {\nm();\n})(angular,CRM.$, CRM._);)'"; // Note: $d has a fundamentally different closure. // Each example string here is a deduped combination of others, // eg "$ab" is the deduping of $a+$b. $ab = "(function (angular, $, _) {\na();\n\nb();\n})(angular,CRM.$,CRM._);"; $abc = "(function (angular, $, _) {\na();\n\nb();\n\nc();\n})(angular,CRM.$, CRM._);"; $cb = "(function( angular, $,_) {\nc();\n\nb();\n})(angular,CRM.$,CRM._);"; $cases = []; $cases[] = [[$a], "$a"]; $cases[] = [[$b], "$b"]; $cases[] = [[$c], "$c"]; $cases[] = [[$d], "$d"]; $cases[] = [[$m], "$m"]; $cases[] = [[$a, $b], "$ab"]; $cases[] = [[$a, $m, $b], "$a$m$b"]; $cases[] = [[$a, $d], "$a$d"]; $cases[] = [[$a, $d, $b], "$a$d$b"]; $cases[] = [[$a, $b, $c], "$abc"]; $cases[] = [[$a, $b, $d, $c, $b], "$ab$d$cb"]; return $cases; } /** * @param array $scripts * @param string $expectedOutput * @dataProvider dedupeClosureExamples */ public function testDedupeClosure($scripts, $expectedOutput) { $actualOutput = CRM_Utils_JS::dedupeClosures( $scripts, ['angular', '$', '_'], ['angular', 'CRM.$', 'CRM._'] ); $this->assertEquals($expectedOutput, implode("", $actualOutput)); } public function stripCommentsExamples() { $cases = []; $cases[] = [ "a();\n//# sourceMappingURL=../foo/bar/baz.js\nb();", "a();\n\nb();", ]; $cases[] = [ "// foo\na();", "\na();", ]; $cases[] = [ "b();\n // foo", "b();\n", ]; $cases[] = [ "/// foo\na();\n\t \t//bar\nb();\n// whiz", "\na();\n\nb();\n", ]; $cases[] = [ "alert('//# sourceMappingURL=../foo/bar/baz.js');\n//zoop\na();", "alert('//# sourceMappingURL=../foo/bar/baz.js');\n\na();", ]; return $cases; } /** * @param string $input * @param string $expectedOutput * @dataProvider stripCommentsExamples */ public function testStripComments($input, $expectedOutput) { $this->assertEquals($expectedOutput, CRM_Utils_JS::stripComments($input)); } public static function decodeExamples() { return [ ['{a: \'Apple\', \'b\': "Banana", c: [1, 2, 3]}', ['a' => 'Apple', 'b' => 'Banana', 'c' => [1, 2, 3]]], ['true', TRUE], [' ', NULL], ['false', FALSE], ['null', NULL], ['"true"', 'true'], ['0.5', 0.5], [" {}", []], ["[]", []], ["{ }", []], [" [ ]", []], [" [ 2 ]", [2]], [ '{a: "parse error no closing bracket"', NULL, ], [ '{a: ["foo", \'bar\'], "b": {a: [\'foo\', "bar"], b: {\'a\': ["foo", "bar"], b: {}}}}', ['a' => ['foo', 'bar'], 'b' => ['a' => ['foo', 'bar'], 'b' => ['a' => ['foo', 'bar'], 'b' => []]]], ], [ ' [{a: {aa: true}, b: [false, null, {x: 1, y: 2, z: 3}] , "c": -1}, ["fee", "fie", \'foe\']]', [['a' => ['aa' => TRUE], 'b' => [FALSE, NULL, ['x' => 1, 'y' => 2, 'z' => 3]], "c" => -1], ["fee", "fie", "foe"]], ], ]; } /** * @param string $input * @param string $expectedOutput * @dataProvider decodeExamples */ public function testDecode($input, $expectedOutput) { $this->assertEquals($expectedOutput, CRM_Utils_JS::decode($input)); } public static function encodeExamples() { return [ [ ['a' => 'Apple', 'b' => 'Banana', 'c' => [0, -2, 3.15]], "{a: 'Apple', b: 'Banana', c: [0, -2, 3.15]}", ], [ ['a' => ['foo', 'bar'], 'b' => ["'a'" => ['foo/bar&', 'bar(foo)'], 'b' => ['a' => ["fo\\\\'oo", '"bar"'], 'b' => []]]], "{a: ['foo', 'bar'], b: {\"'a'\": ['foo/bar&', 'bar(foo)'], b: {a: ['fo\\\\\\\\\\'oo', '\"bar\"'], b: {}}}}", ], [TRUE, 'true'], [' ', "' '"], [FALSE, 'false'], [NULL, 'null'], ['true', "'true'"], ['"false"', "'\"false\"'"], ['0.5', "'0.5'"], [0.5, '0.5'], [[], "{}"], ]; } /** * @param string $input * @param string $expectedOutput * @dataProvider encodeExamples */ public function testEncode($input, $expectedOutput) { $result = CRM_Utils_JS::encode($input); $this->assertEquals($expectedOutput, $result); $this->assertEquals($input, CRM_Utils_JS::decode($result)); } /** * @return array */ public static function objectExamples() { return [ [ '{a: \'Apple\', \'b\': "Banana", "c ": [1,2,3]}', ['a' => "'Apple'", 'b' => '"Banana"', 'c ' => '[1,2,3]'], '{a: \'Apple\', b: "Banana", \'c \': [1,2,3]}', ], [ " {}", [], "{}", ], [ " [ ] ", [], "{}", ], [ " {'fn' : function (foo, bar, baz) { return \"One, two, three\"; }, esc: /[1-9]\\\\/.test('5\\\\') , number : 55.5/2 } ", ['fn' => 'function (foo, bar, baz) { return "One, two, three"; }', 'esc' => "/[1-9]\\\\/.test('5\\\\')", 'number' => '55.5/2'], "{fn: function (foo, bar, baz) { return \"One, two, three\"; }, esc: /[1-9]\\\\/.test('5\\\\'), number: 55.5/2}", ], [ "{ string : 'this, has(some : weird, \\'stuff [{}!' , expr: sum(1, 2, 3) / 2 + 1, ' notes ' : [Do, re mi], }", ['string' => "'this, has(some : weird, \\'stuff [{}!'", 'expr' => 'sum(1, 2, 3) / 2 + 1', ' notes ' => "[Do, re mi]"], "{string: 'this, has(some : weird, \\'stuff [{}!', expr: sum(1, 2, 3) / 2 + 1, ' notes ': [Do, re mi]}", ], [ '{status: /^http:\/\/civicrm\.com/.test(url) ? \'good\' : \'bad\' , \'foo\&\': getFoo("Some \"quoted\" thing"), "ba\'[(r": function() {return "bar"}}', ['status' => '/^http:\/\/civicrm\.com/.test(url) ? \'good\' : \'bad\'', 'foo&' => 'getFoo("Some \"quoted\" thing")', "ba'[(r" => 'function() {return "bar"}'], '{status: /^http:\/\/civicrm\.com/.test(url) ? \'good\' : \'bad\', "foo&": getFoo("Some \"quoted\" thing"), "ba\'[(r": function() {return "bar"}}', ], [ '{"some\"key": typeof foo === \'number\' ? true : false , "O\'Really?": ",((,", \'A"quote"\': 1 + 1 , "\\\\\\&\\/" : 0}', ['some"key' => 'typeof foo === \'number\' ? true : false', "O'Really?" => '",((,"', 'A"quote"' => '1 + 1', '\\&/' => '0'], '{\'some"key\': typeof foo === \'number\' ? true : false, "O\'Really?": ",((,", \'A"quote"\': 1 + 1, "\\\\&/": 0}', ], [ '[foo ? 1 : 2 , 3 , function() {return 1 + 1;}, /^http:\/\/civicrm\.com/.test(url) ? \'good\' : \'bad\' , 3.14 ]', ['foo ? 1 : 2', '3', 'function() {return 1 + 1;}', '/^http:\/\/civicrm\.com/.test(url) ? \'good\' : \'bad\'', '3.14'], '[foo ? 1 : 2, 3, function() {return 1 + 1;}, /^http:\/\/civicrm\.com/.test(url) ? \'good\' : \'bad\', 3.14]', ], ]; } /** * Test converting a js string to a php array and back again. * * @param string $input * @param string $expectedPHP * @param $expectedJS * @dataProvider objectExamples */ public function testObjectToAndFromString($input, $expectedPHP, $expectedJS) { $objectProps = CRM_Utils_JS::getRawProps($input); $this->assertEquals($expectedPHP, $objectProps); $reformattedJS = CRM_Utils_JS::writeObject($objectProps); $this->assertEquals($expectedJS, $reformattedJS); $this->assertEquals($expectedPHP, CRM_Utils_JS::getRawProps($reformattedJS)); } }