@@ -112,6 +112,291 @@ public void itDefersImport() {
112112 assertThat (spacer .isDeferred ()).isTrue ();
113113 }
114114
115+ @ Test
116+ public void itResolvesNestedRelativeImports () throws Exception {
117+ if (interpreter .getConfig ().getExecutionMode ().useEagerParser ()) {
118+ return ;
119+ }
120+ jinjava .setResourceLocator (
121+ new ResourceLocator () {
122+ private final RelativePathResolver relativePathResolver =
123+ new RelativePathResolver ();
124+ private final java .util .Map <String , String > templates =
125+ new java .util .HashMap <>() {
126+ {
127+ put (
128+ "level0.jinja" ,
129+ "{% from 'level1/nested.jinja' import macro1 %}{{ macro1() }}"
130+ );
131+ put (
132+ "level1/nested.jinja" ,
133+ "{% from '../level1/deeper/macro.jinja' import macro2 %}{% macro macro1() %}L1:{{ macro2() }}{% endmacro %}"
134+ );
135+ put (
136+ "level1/deeper/macro.jinja" ,
137+ "{% from '../../utils/helper.jinja' import helper %}{% macro macro2() %}L2:{{ helper() }}{% endmacro %}"
138+ );
139+ put ("utils/helper.jinja" , "{% macro helper() %}HELPER{% endmacro %}" );
140+ }
141+ };
142+
143+ @ Override
144+ public String getString (
145+ String fullName ,
146+ Charset encoding ,
147+ JinjavaInterpreter interpreter
148+ ) throws IOException {
149+ String template = templates .get (fullName );
150+ if (template == null ) {
151+ throw new IOException ("Template not found: " + fullName );
152+ }
153+ return template ;
154+ }
155+
156+ @ Override
157+ public Optional <LocationResolver > getLocationResolver () {
158+ return Optional .of (relativePathResolver );
159+ }
160+ }
161+ );
162+
163+ interpreter .getContext ().getCurrentPathStack ().push ("level0.jinja" , 1 , 0 );
164+ String result = interpreter .render (interpreter .getResource ("level0.jinja" ));
165+
166+ assertThat (interpreter .getErrors ()).isEmpty ();
167+ assertThat (result .trim ()).isEqualTo ("L1:L2:HELPER" );
168+ }
169+
170+ @ Test
171+ public void itMaintainsPathStackIntegrity () throws Exception {
172+ if (interpreter .getConfig ().getExecutionMode ().useEagerParser ()) {
173+ return ;
174+ }
175+ jinjava .setResourceLocator (
176+ new ResourceLocator () {
177+ private final RelativePathResolver relativePathResolver =
178+ new RelativePathResolver ();
179+ private final java .util .Map <String , String > templates =
180+ new java .util .HashMap <>() {
181+ {
182+ put (
183+ "root.jinja" ,
184+ "{% from 'simple/macro.jinja' import simple_macro %}{{ simple_macro() }}"
185+ );
186+ put ("simple/macro.jinja" , "{% macro simple_macro() %}SIMPLE{% endmacro %}" );
187+ }
188+ };
189+
190+ @ Override
191+ public String getString (
192+ String fullName ,
193+ Charset encoding ,
194+ JinjavaInterpreter interpreter
195+ ) throws IOException {
196+ String template = templates .get (fullName );
197+ if (template == null ) {
198+ throw new IOException ("Template not found: " + fullName );
199+ }
200+ return template ;
201+ }
202+
203+ @ Override
204+ public Optional <LocationResolver > getLocationResolver () {
205+ return Optional .of (relativePathResolver );
206+ }
207+ }
208+ );
209+
210+ interpreter .getContext ().getCurrentPathStack ().push ("root.jinja" , 1 , 0 );
211+ int initialStackSize = interpreter .getContext ().getCurrentPathStack ().size ();
212+
213+ interpreter .render (interpreter .getResource ("root.jinja" ));
214+
215+ assertThat (interpreter .getContext ().getCurrentPathStack ().size ())
216+ .isEqualTo (initialStackSize );
217+ assertThat (interpreter .getErrors ()).isEmpty ();
218+ }
219+
220+ @ Test
221+ public void itWorksWithIncludeAndFromTogether () throws Exception {
222+ if (interpreter .getConfig ().getExecutionMode ().useEagerParser ()) {
223+ return ;
224+ }
225+ jinjava .setResourceLocator (
226+ new ResourceLocator () {
227+ private final RelativePathResolver relativePathResolver =
228+ new RelativePathResolver ();
229+ private final java .util .Map <String , String > templates =
230+ new java .util .HashMap <>() {
231+ {
232+ put (
233+ "mixed-tags.jinja" ,
234+ "{% from 'macros/test.jinja' import test_macro %}{% include 'includes/content.jinja' %}{{ test_macro() }}"
235+ );
236+ put (
237+ "macros/test.jinja" ,
238+ "{% from '../utils/shared.jinja' import shared %}{% macro test_macro() %}MACRO:{{ shared() }}{% endmacro %}"
239+ );
240+ put (
241+ "includes/content.jinja" ,
242+ "{% from '../utils/shared.jinja' import shared %}INCLUDE:{{ shared() }}"
243+ );
244+ put ("utils/shared.jinja" , "{% macro shared() %}SHARED{% endmacro %}" );
245+ }
246+ };
247+
248+ @ Override
249+ public String getString (
250+ String fullName ,
251+ Charset encoding ,
252+ JinjavaInterpreter interpreter
253+ ) throws IOException {
254+ String template = templates .get (fullName );
255+ if (template == null ) {
256+ throw new IOException ("Template not found: " + fullName );
257+ }
258+ return template ;
259+ }
260+
261+ @ Override
262+ public Optional <LocationResolver > getLocationResolver () {
263+ return Optional .of (relativePathResolver );
264+ }
265+ }
266+ );
267+
268+ interpreter .getContext ().getCurrentPathStack ().push ("mixed-tags.jinja" , 1 , 0 );
269+ String result = interpreter .render (interpreter .getResource ("mixed-tags.jinja" ));
270+
271+ assertThat (interpreter .getErrors ()).isEmpty ();
272+ assertThat (result .trim ()).contains ("INCLUDE:SHARED" );
273+ assertThat (result .trim ()).contains ("MACRO:SHARED" );
274+ }
275+
276+ @ Test
277+ public void itResolvesUpAndAcrossDirectoryPaths () throws Exception {
278+ if (interpreter .getConfig ().getExecutionMode ().useEagerParser ()) {
279+ return ;
280+ }
281+ jinjava .setResourceLocator (
282+ new ResourceLocator () {
283+ private final RelativePathResolver relativePathResolver =
284+ new RelativePathResolver ();
285+ private final java .util .Map <String , String > templates =
286+ new java .util .HashMap <>() {
287+ {
288+ put (
289+ "theme/hubl-modules/navigation.module/module.hubl.html" ,
290+ "{% from '../../partials/atoms/link/link.hubl.html' import link_macro %}{{ link_macro() }}"
291+ );
292+ put (
293+ "theme/partials/atoms/link/link.hubl.html" ,
294+ "{% from '../icons/icons.hubl.html' import icon_macro %}{% macro link_macro() %}LINK:{{ icon_macro() }}{% endmacro %}"
295+ );
296+ put (
297+ "theme/partials/atoms/icons/icons.hubl.html" ,
298+ "{% macro icon_macro() %}ICON{% endmacro %}"
299+ );
300+ }
301+ };
302+
303+ @ Override
304+ public String getString (
305+ String fullName ,
306+ Charset encoding ,
307+ JinjavaInterpreter interpreter
308+ ) throws IOException {
309+ String template = templates .get (fullName );
310+ if (template == null ) {
311+ throw new IOException ("Template not found: " + fullName );
312+ }
313+ return template ;
314+ }
315+
316+ @ Override
317+ public Optional <LocationResolver > getLocationResolver () {
318+ return Optional .of (relativePathResolver );
319+ }
320+ }
321+ );
322+
323+ interpreter
324+ .getContext ()
325+ .getCurrentPathStack ()
326+ .push ("theme/hubl-modules/navigation.module/module.hubl.html" , 1 , 0 );
327+ String result = interpreter .render (
328+ interpreter .getResource ("theme/hubl-modules/navigation.module/module.hubl.html" )
329+ );
330+
331+ assertThat (interpreter .getErrors ()).isEmpty ();
332+ assertThat (result .trim ()).isEqualTo ("LINK:ICON" );
333+ }
334+
335+ @ Test
336+ public void itResolvesOriginalErrorCasePaths () throws Exception {
337+ if (interpreter .getConfig ().getExecutionMode ().useEagerParser ()) {
338+ return ;
339+ }
340+ jinjava .setResourceLocator (
341+ new ResourceLocator () {
342+ private final RelativePathResolver relativePathResolver =
343+ new RelativePathResolver ();
344+ private final java .util .Map <String , String > templates =
345+ new java .util .HashMap <>() {
346+ {
347+ put (
348+ "@projects/mws-theme-minimal/theme/hubl-modules/navigation.module/module.hubl.html" ,
349+ "{% from '../../partials/atoms/link/link.hubl.html' import button %}{{ button() }}"
350+ );
351+ put (
352+ "@projects/mws-theme-minimal/theme/partials/atoms/link/link.hubl.html" ,
353+ "{% from '../icons/icons.hubl.html' import get_icon %}{% macro button() %}{{ get_icon() }}{% endmacro %}"
354+ );
355+ put (
356+ "@projects/mws-theme-minimal/theme/partials/atoms/icons/icons.hubl.html" ,
357+ "{% macro get_icon() %}ICON{% endmacro %}"
358+ );
359+ }
360+ };
361+
362+ @ Override
363+ public String getString (
364+ String fullName ,
365+ Charset encoding ,
366+ JinjavaInterpreter interpreter
367+ ) throws IOException {
368+ String template = templates .get (fullName );
369+ if (template == null ) {
370+ throw new IOException ("Template not found: " + fullName );
371+ }
372+ return template ;
373+ }
374+
375+ @ Override
376+ public Optional <LocationResolver > getLocationResolver () {
377+ return Optional .of (relativePathResolver );
378+ }
379+ }
380+ );
381+
382+ interpreter
383+ .getContext ()
384+ .getCurrentPathStack ()
385+ .push (
386+ "@projects/mws-theme-minimal/theme/hubl-modules/navigation.module/module.hubl.html" ,
387+ 1 ,
388+ 0
389+ );
390+ String result = interpreter .render (
391+ interpreter .getResource (
392+ "@projects/mws-theme-minimal/theme/hubl-modules/navigation.module/module.hubl.html"
393+ )
394+ );
395+
396+ assertThat (interpreter .getErrors ()).isEmpty ();
397+ assertThat (result .trim ()).isEqualTo ("ICON" );
398+ }
399+
115400 private String fixture (String name ) {
116401 return interpreter .renderFlat (fixtureText (name ));
117402 }
0 commit comments