1+ <?php namespace lang \ast \syntax \php \unittest ;
2+
3+ use lang \ast \Errors ;
4+ use lang \ast \unittest \emit \EmittingTest ;
5+ use test \{Assert , Before , Expect , Test };
6+
7+ class TemplateLiteralsTest extends EmittingTest {
8+ private $ format ;
9+
10+ /** Evaluates a strign template */
11+ private function evaluate (string $ template , array $ arguments = []) {
12+ return $ this ->type ('class %T { public function run($f, $arguments) { return ' .$ template .'; } } ' )
13+ ->newInstance ()
14+ ->run ($ this ->format , $ arguments )
15+ ;
16+ }
17+
18+ #[Before]
19+ public function format () {
20+ $ this ->format = function ($ strings , ... $ arguments ) {
21+ $ r = '' ;
22+ foreach ($ strings as $ i => $ string ) {
23+ $ r .= $ string .htmlspecialchars ($ arguments [$ i ] ?? '' );
24+ }
25+ return $ r ;
26+ };
27+ }
28+
29+ #[Test]
30+ public function without_placeholders () {
31+ Assert::equals ('Test ' , $ this ->evaluate ('$f`Test` ' ));
32+ }
33+
34+ #[Test]
35+ public function escaped_backtick () {
36+ Assert::equals ('Command: `ls -al` ' , $ this ->evaluate ('$f`Command: \`ls -al\`` ' ));
37+ }
38+
39+ #[Test]
40+ public function support_escape_sequences () {
41+ Assert::equals ("A \n€ " , $ this ->evaluate ('$f`A\n\u{20ac}` ' ));
42+ }
43+
44+ #[Test]
45+ public function does_not_interpolate_variables () {
46+ Assert::equals ('Used $literally ' , $ this ->evaluate ('$f`Used $literally` ' ));
47+ }
48+
49+ #[Test]
50+ public function placeholder_at_beginning () {
51+ Assert::equals ('test ' , $ this ->evaluate ('$f`${"test"}` ' ));
52+ }
53+
54+ #[Test]
55+ public function evaluates_arguments () {
56+ Assert::equals ('2 + 3 = 5 ' , $ this ->evaluate ('$f`2 + 3 = ${2 + 3}` ' ));
57+ }
58+
59+ #[Test]
60+ public function dollar_sign () {
61+ Assert::equals ('Price is $1.99 ' , $ this ->evaluate ('$f`Price is $${1.99}` ' ));
62+ }
63+
64+ #[Test]
65+ public function braces () {
66+ Assert::equals ('Supported on ' .PHP_OS , $ this ->evaluate ('$f`Supported on ${match (true) { default => PHP_OS }}` ' ));
67+ }
68+
69+ #[Test]
70+ public function evaluates_global_constant () {
71+ Assert::equals ('PHP_OS = ' .PHP_OS , $ this ->evaluate ('$f`PHP_OS = ${PHP_OS}` ' ));
72+ }
73+
74+ #[Test]
75+ public function argument_passed () {
76+ Assert::equals (
77+ 'This is a <a href="https://example.com/?a&b">link</a>. ' ,
78+ $ this ->evaluate ('$f`This is a <a href="${$arguments[0]}">link</a>.` ' , ['https://example.com/?a&b ' ])
79+ );
80+ }
81+
82+ #[Test]
83+ public function quoted_string () {
84+ Assert::equals (
85+ 'He said "Test" ' ,
86+ $ this ->evaluate ('$f`He said "Test"` ' )
87+ );
88+ }
89+
90+ #[Test]
91+ public function ternary_expression () {
92+ Assert::equals (
93+ 'You are 17, you can not yet vote. ' ,
94+ $ this ->evaluate ('$f`You are ${$arguments[0]}, you can ${$arguments[0] < 18 ? "not yet vote" : "vote"}.` ' , [17 ])
95+ );
96+ }
97+
98+ #[Test]
99+ public function resolve_via_method () {
100+ $ t = $ this ->type ('class %T {
101+ private function plain($strings, ... $arguments) {
102+ return implode("", $strings);
103+ }
104+
105+ public function run() {
106+ return $this->plain`Test`;
107+ }
108+ } ' );
109+
110+ Assert::equals ('Test ' , $ t ->newInstance ()->run ());
111+ }
112+
113+ #[Test]
114+ public function can_return_other_types_than_string () {
115+ $ t = $ this ->type ('class %T {
116+ private $base= "//example.com";
117+
118+ private function resource($strings, ... $arguments) {
119+ $path= "";
120+ foreach ($strings as $i => $string) {
121+ $path.= $string.rawurlencode($arguments[$i] ?? "");
122+ }
123+ return ["base" => $this->base, "path" => $path];
124+ }
125+
126+ public function run($userId) {
127+ return $this->resource`/users/${$userId}`;
128+ }
129+ } ' );
130+
131+ Assert::equals (['base ' => '//example.com ' , 'path ' => '/users/%40me ' ], $ t ->newInstance ()->run ('@me ' ));
132+ }
133+
134+ #[Test, Expect(class: Errors::class, message: '/Unexpected string "Test" \[line 1 of .+\]/ ' )]
135+ public function cannot_suffix_other_literals () {
136+ $ this ->evaluate ('$f"Test" ' );
137+ }
138+ }
0 commit comments