22
33namespace Tests \Antlers \Runtime ;
44
5+ use Statamic \Facades \Antlers ;
6+ use Statamic \Facades \Cascade ;
57use Statamic \View \Antlers \Language \Runtime \PathDataManager ;
68use Tests \Antlers \ParserTestCase ;
79
@@ -14,6 +16,13 @@ private function getPathValue($path, $data)
1416 return $ dataRetriever ->getData ($ this ->parsePath ($ path ), $ data );
1517 }
1618
19+ private function getPathValueWithExistence ($ path , $ data )
20+ {
21+ $ dataRetriever = new PathDataManager ();
22+
23+ return $ dataRetriever ->getDataWithExistence ($ this ->parsePath ($ path ), $ data );
24+ }
25+
1726 public function test_simple_data_is_retrieved ()
1827 {
1928 $ data = [
@@ -76,4 +85,218 @@ public function test_dynamic_keys_are_correctly_set()
7685 $ value = $ this ->getPathValue ('page[view:nested:nested1:nested2] ' , $ data );
7786 $ this ->assertSame (12345 , $ value );
7887 }
88+
89+ public function test_object_properties_are_retrieved ()
90+ {
91+ $ data = [
92+ 'view ' => [
93+ 'object ' => new class
94+ {
95+ public string $ publicProperty = 'Hello Public World! ' ;
96+
97+ protected string $ protectedProperty = 'Hello Protected World! ' ;
98+
99+ private string $ privateProperty = 'Hello Private World! ' ;
100+ },
101+ ],
102+ ];
103+
104+ $ value = $ this ->getPathValue ('view.object.public_property ' , $ data );
105+ $ this ->assertSame ('Hello Public World! ' , $ value );
106+
107+ $ value = $ this ->getPathValue ('view.object.protected_property ' , $ data );
108+ $ this ->assertNull ($ value );
109+
110+ $ value = $ this ->getPathValue ('view.object.private_property ' , $ data );
111+ $ this ->assertNull ($ value );
112+ }
113+
114+ public function test_object_properties_are_usable_in_antlers_conditions ()
115+ {
116+ $ object = new class
117+ {
118+ public string $ truthyStringProperty = 'Hello Public World! ' ;
119+
120+ public bool $ truthyBoolProperty = true ;
121+
122+ public string $ falsyStringProperty = '' ;
123+
124+ public bool $ falsyBoolProperty = false ;
125+
126+ protected string $ protectedProperty = 'Hello Protected World! ' ;
127+
128+ private string $ privateProperty = 'Hello Private World! ' ;
129+ };
130+
131+ Cascade::set ('object ' , $ object );
132+
133+ $ value = (string ) Antlers::parse ('{{ if object:truthy_string_property }}yes{{ else }}no{{ /if }} ' );
134+ $ this ->assertSame ('yes ' , $ value );
135+
136+ $ value = (string ) Antlers::parse ('{{ if object:truthy_bool_property }}yes{{ else }}no{{ /if }} ' );
137+ $ this ->assertSame ('yes ' , $ value );
138+
139+ $ value = (string ) Antlers::parse ('{{ if object:falsy_string_property }}yes{{ else }}no{{ /if }} ' );
140+ $ this ->assertSame ('no ' , $ value );
141+
142+ $ value = (string ) Antlers::parse ('{{ if object:falsy_bool_property }}yes{{ else }}no{{ /if }} ' );
143+ $ this ->assertSame ('no ' , $ value );
144+
145+ $ value = (string ) Antlers::parse ('{{ if object:protected_property }}yes{{ else }}no{{ /if }} ' );
146+ $ this ->assertSame ('no ' , $ value );
147+
148+ $ value = (string ) Antlers::parse ('{{ if object:private_property }}yes{{ else }}no{{ /if }} ' );
149+ $ this ->assertSame ('no ' , $ value );
150+ }
151+
152+ public function test_non_public_methods_are_not_called ()
153+ {
154+ $ data = [
155+ 'object ' => new class
156+ {
157+ protected function protectedMethod ()
158+ {
159+ return 'Protected Method ' ;
160+ }
161+
162+ private function privateMethod ()
163+ {
164+ return 'Private Method ' ;
165+ }
166+ },
167+ ];
168+
169+ $ value = $ this ->getPathValue ('object.protected_method ' , $ data );
170+ $ this ->assertNull ($ value );
171+
172+ $ value = $ this ->getPathValue ('object.private_method ' , $ data );
173+ $ this ->assertNull ($ value );
174+ }
175+
176+ public function test_public_property_is_used_when_non_public_method_shares_name ()
177+ {
178+ $ data = [
179+ 'object ' => new class
180+ {
181+ public string $ label = 'My Widget ' ;
182+
183+ protected function label ()
184+ {
185+ return strtoupper ($ this ->label );
186+ }
187+ },
188+ ];
189+
190+ $ value = $ this ->getPathValue ('object.label ' , $ data );
191+ $ this ->assertSame ('My Widget ' , $ value );
192+ }
193+
194+ public function test_objects_with_no_matching_property_or_method_are_returned_as_null ()
195+ {
196+ $ data = [
197+ 'object ' => new class
198+ {
199+ },
200+ ];
201+
202+ $ value = $ this ->getPathValue ('object.no_existent ' , $ data );
203+ $ this ->assertNull ($ value );
204+
205+ Cascade::set ('object ' , $ data ['object ' ]);
206+
207+ $ value = (string ) Antlers::parse ('{{ if object:no_existent }}yes{{ else }}no{{ /if }} ' );
208+ $ this ->assertSame ('no ' , $ value );
209+ }
210+
211+ public function test_non_public_properties_report_not_found ()
212+ {
213+ $ data = [
214+ 'view ' => [
215+ 'object ' => new class
216+ {
217+ protected string $ protectedProperty = 'Hello Protected World! ' ;
218+
219+ private string $ privateProperty = 'Hello Private World! ' ;
220+ },
221+ ],
222+ ];
223+
224+ [$ found , $ value ] = $ this ->getPathValueWithExistence ('view.object.protected_property ' , $ data );
225+ $ this ->assertFalse ($ found );
226+ $ this ->assertNull ($ value );
227+
228+ [$ found , $ value ] = $ this ->getPathValueWithExistence ('view.object.private_property ' , $ data );
229+ $ this ->assertFalse ($ found );
230+ $ this ->assertNull ($ value );
231+ }
232+
233+ public function test_non_public_properties_short_circuit_deeper_paths ()
234+ {
235+ $ data = [
236+ 'object ' => new class
237+ {
238+ protected string $ protectedProperty = 'Hello Protected World! ' ;
239+ },
240+ ];
241+
242+ $ value = $ this ->getPathValue ('object.protected_property.deeper.path ' , $ data );
243+ $ this ->assertNull ($ value );
244+
245+ [$ found , $ value ] = $ this ->getPathValueWithExistence ('object.protected_property.deeper.path ' , $ data );
246+ $ this ->assertFalse ($ found );
247+ $ this ->assertNull ($ value );
248+ }
249+
250+ public function test_objects_with_no_matching_property_or_method_report_not_found ()
251+ {
252+ $ data = [
253+ 'object ' => new class
254+ {
255+ },
256+ ];
257+
258+ [$ found , $ value ] = $ this ->getPathValueWithExistence ('object.no_existent ' , $ data );
259+ $ this ->assertFalse ($ found );
260+ $ this ->assertNull ($ value );
261+ }
262+
263+ public function test_objects_with_no_matching_property_short_circuit_deeper_paths ()
264+ {
265+ $ data = [
266+ 'object ' => new class
267+ {
268+ public string $ name = 'Hello ' ;
269+ },
270+ ];
271+
272+ $ value = $ this ->getPathValue ('object.no_existent.deeper.path ' , $ data );
273+ $ this ->assertNull ($ value );
274+
275+ [$ found , $ value ] = $ this ->getPathValueWithExistence ('object.no_existent.deeper.path ' , $ data );
276+ $ this ->assertFalse ($ found );
277+ $ this ->assertNull ($ value );
278+ }
279+
280+ public function test_exact_name_non_public_properties_are_not_accessed ()
281+ {
282+ $ data = [
283+ 'object ' => new class
284+ {
285+ public string $ name = 'Public ' ;
286+
287+ protected string $ secret = 'Protected ' ;
288+
289+ private string $ hidden = 'Private ' ;
290+ },
291+ ];
292+
293+ $ value = $ this ->getPathValue ('object.name ' , $ data );
294+ $ this ->assertSame ('Public ' , $ value );
295+
296+ $ value = $ this ->getPathValue ('object.secret ' , $ data );
297+ $ this ->assertNull ($ value );
298+
299+ $ value = $ this ->getPathValue ('object.hidden ' , $ data );
300+ $ this ->assertNull ($ value );
301+ }
79302}
0 commit comments