11import { shallowMount , flushPromises } from '@vue/test-utils'
22import App from '../../src/App.vue'
33
4+ // mock XMLHttpRequest for templates loading
5+ global . XMLHttpRequest = class XMLHttpRequest {
6+ private responseText : string
7+ private status : number
8+
9+ open ( method , templatePath ) {
10+ const templateName = templatePath . split ( '/' ) . pop ( )
11+
12+ switch ( templateName ) {
13+ case 'cover-template.html' :
14+ this . status = 200
15+ this . responseText = `<script type="x-tmpl-mustache">
16+ <div class="content-container">
17+ <div class="logo">
18+ <img src="{{{ metadata.logo }}}" alt="Logo">
19+ </div>
20+
21+ <div class="content">
22+ <h1>{{{ title }}}</h1>
23+
24+ <p>
25+ By: {{{ metadata.presenter }}}
26+ </p>
27+
28+ </div>
29+ </div>
30+ </script>
31+ `
32+ break
33+ case 'title-content-template.html' :
34+ this . status = 200
35+ this . responseText = `<script type="x-tmpl-mustache">
36+ <div class="content-container">
37+ <div class="title">
38+ <h1>
39+
40+ {{{ title }}}
41+
42+ </h1>
43+ <div class="logo">
44+ <img src="{{{ metadata.logo }}}" alt="Logo">
45+ </div>
46+ </div>
47+
48+ <div class="content-wrapper">
49+ <div class="content">
50+
51+ {{{ content }}}
52+
53+ </div>
54+ </div>
55+ </div>
56+ <footer>
57+ <div class="footer-content">{{{ metadata.footer }}}</div>
58+ <div class="custom-slide-number"></div>
59+ </footer>
60+ </script>
61+ `
62+ break
63+ case 'title-content-image-template.html' :
64+ this . status = 200
65+ this . responseText = `<script type="x-tmpl-mustache">
66+ <div class="content-container">
67+ <div class="title">
68+ <h1>
69+
70+ {{{ title }}}
71+
72+ </h1>
73+ <div class="logo">
74+ <img src="{{{ metadata.logo }}}" alt="Logo">
75+ </div>
76+ </div>
77+
78+ <div class="content-wrapper">
79+ <div class="content">
80+
81+ {{{ content }}}
82+
83+ </div>
84+ </div>
85+ </div>
86+ <footer>
87+ <div class="footer-content">{{{ metadata.footer }}}</div>
88+ <div class="custom-slide-number"></div>
89+ </footer>
90+ </script>
91+ `
92+ break
93+ case 'about-us-template.html' :
94+ this . status = 200
95+ this . responseText = `<script type="x-tmpl-mustache">
96+ <div class="content-container">
97+ <div class="title">
98+ <h1>
99+
100+ {{{ title }}}
101+
102+ </h1>
103+ <div class="logo">
104+ <img src="{{{ metadata.logo }}}" alt="Logo">
105+ </div>
106+ </div>
107+
108+ <div class="content-wrapper">
109+ <div class="content">
110+ <div class="about-us-text">
111+
112+ {{{ content }}}
113+
114+ </div>
115+ <div class="info-section">
116+
117+ {{#metadata.aboutUs}}
118+ <div class="info-box">
119+ <h3>{{ title }}</h3>
120+ <p>{{ text }}</p>
121+ </div>
122+ <div class="divider"></div>
123+ {{/metadata.aboutUs}}
124+
125+ </div>
126+
127+ </div>
128+ </div>
129+ </div>
130+ <footer>
131+ <div class="footer-content">{{{ metadata.footer }}}</div>
132+ <div class="custom-slide-number"></div>
133+ </footer>
134+ </script>
135+ `
136+ break
137+ default :
138+ this . status = 404
139+ return '<p>Template for slide "' + templateName + '" not found.</p>'
140+ }
141+ }
142+
143+ send ( ) {
144+ return
145+ }
146+ }
147+
4148// mock modules
5149vi . mock ( '@ownclouders/web-pkg' , ( ) => ( {
6150 useAppDefaults : vi . fn ( ) . mockImplementation ( ( ) => ( {
@@ -35,7 +179,7 @@ vi.mock('@ownclouders/web-pkg', () => ({
35179 AppLoadingSpinner : vi . fn ( )
36180} ) )
37181// global mocks
38- global . fetch = vi . fn ( ) . mockImplementation ( ( ) =>
182+ const defaultFetchMock = vi . fn ( ) . mockImplementation ( ( ) =>
39183 Promise . resolve ( {
40184 text : ( ) =>
41185 Promise . resolve ( `### Slide 1
@@ -96,6 +240,7 @@ code block
96240 )
97241 } )
98242)
243+ global . fetch = defaultFetchMock
99244URL . createObjectURL = vi
100245 . fn ( )
101246 . mockImplementation ( ( ) => 'blob:nodedata:0295bafb-5976-468a-a263-685a8872cb96' )
@@ -113,3 +258,183 @@ function getWrapper() {
113258 propsData : { url : 'https://localhost:9200/slides.md' }
114259 } )
115260}
261+
262+ // eslint-disable-next-line require-await
263+ describe ( 'Template Features' , async ( ) => {
264+ beforeEach ( ( ) => {
265+ global . fetch = defaultFetchMock
266+ } )
267+
268+ it ( 'should return template not found error message' , async ( ) => {
269+ global . fetch = vi . fn ( ) . mockImplementation ( ( ) => {
270+ return Promise . resolve ( {
271+ text : ( ) =>
272+ Promise . resolve ( `---
273+ slide: non-existent
274+ ---
275+ ` ) ,
276+ blob : ( ) => Promise . resolve ( new Blob ( [ ] , { type : 'text/markdown' } ) )
277+ } )
278+ } )
279+ const vm = getWrapper ( )
280+ await flushPromises ( )
281+ expect ( vm . html ( ) ) . toContain ( 'Template for slide "non-existent" not found.' )
282+ } )
283+
284+ it ( 'should return yaml parsing error' , async ( ) => {
285+ const errorSpy = vi . spyOn ( console , 'error' ) . mockImplementation ( ( ) => { } )
286+ global . fetch = vi . fn ( ) . mockImplementation ( ( ) => {
287+ return Promise . resolve ( {
288+ text : ( ) =>
289+ Promise . resolve ( `---
290+ slide: title-content
291+ logo: [invalid yaml
292+ ---
293+ ` ) ,
294+ blob : ( ) => Promise . resolve ( new Blob ( [ ] , { type : 'text/markdown' } ) )
295+ } )
296+ } )
297+
298+ const vm = getWrapper ( )
299+ await flushPromises ( )
300+
301+ expect ( errorSpy ) . toHaveBeenCalledWith (
302+ 'Frontmatter parsing error:' ,
303+ expect . objectContaining ( {
304+ name : 'YAMLException' ,
305+ message : expect . stringContaining ( 'unexpected end of the stream within a flow collection' )
306+ } )
307+ )
308+ expect ( vm . html ( ) ) . toContain (
309+ 'YAMLException: unexpected end of the stream within a flow collection at line 3, column 1'
310+ )
311+ } )
312+
313+ it ( 'should render cover template' , async ( ) => {
314+ global . fetch = vi . fn ( ) . mockImplementation ( ( ) => {
315+ return Promise . resolve ( {
316+ text : ( ) =>
317+ Promise . resolve ( `---
318+ slide: title-content
319+ presenter: John Doe
320+ logo: https://external:9200/cat.jpg
321+ ---
322+ # Reveal Js Templates in Web App Presentation Viewer ::slide:cover
323+ ` ) ,
324+ blob : ( ) => Promise . resolve ( new Blob ( [ ] , { type : 'text/markdown' } ) )
325+ } )
326+ } )
327+ const vm = getWrapper ( )
328+ await flushPromises ( )
329+ await vi . waitFor (
330+ ( ) => {
331+ expect ( vm . classes ( 'md-template' ) ) . toBe ( true )
332+ } ,
333+ { timeout : 1000 }
334+ )
335+ expect ( vm . find ( '.reveal .slides' ) . html ( ) ) . toMatchSnapshot ( )
336+ } )
337+
338+ it ( 'should render title content template' , async ( ) => {
339+ global . fetch = vi . fn ( ) . mockImplementation ( ( ) => {
340+ return Promise . resolve ( {
341+ text : ( ) =>
342+ Promise . resolve ( `---
343+ slide: title-content
344+ presenter: John Doe
345+ logo: https://external:9200/cat.jpg
346+ ---
347+ # Title Content Slide
348+
349+ - Introduction to mountain ecosystems
350+ - Basic concepts of quantum encryption
351+ - Exploring culinary traditions in Southeast Asia
352+ - Overview of blockchain consensus mechanisms
353+ - Setting up a personal productivity system
354+ - Writing clean, maintainable JavaScript code
355+ - Understanding modern art movements
356+ - History of aviation and early flight experiments
357+ - Managing team dynamics in remote work
358+ ` ) ,
359+ blob : ( ) => Promise . resolve ( new Blob ( [ ] , { type : 'text/markdown' } ) )
360+ } )
361+ } )
362+ const vm = getWrapper ( )
363+ await flushPromises ( )
364+ await vi . waitFor (
365+ ( ) => {
366+ expect ( vm . classes ( 'md-template' ) ) . toBe ( true )
367+ } ,
368+ { timeout : 1000 }
369+ )
370+ expect ( vm . find ( '.reveal .slides' ) . html ( ) ) . toMatchSnapshot ( )
371+ } )
372+
373+ it ( 'should render title content image template' , async ( ) => {
374+ global . fetch = vi . fn ( ) . mockImplementation ( ( ) => {
375+ return Promise . resolve ( {
376+ text : ( ) =>
377+ Promise . resolve ( `---
378+ slide: title-content
379+ presenter: John Doe
380+ logo: https://external:9200/cat.jpg
381+ ---
382+ # Title Content Image Slide ::slide:title-content-image
383+
384+ - Introduction to mountain ecosystems
385+ - Basic concepts of quantum encryption
386+ - Exploring culinary traditions in Southeast Asia
387+ - Overview of blockchain consensus mechanisms
388+
389+ 
390+ ` ) ,
391+ blob : ( ) => Promise . resolve ( new Blob ( [ ] , { type : 'text/markdown' } ) )
392+ } )
393+ } )
394+ const vm = getWrapper ( )
395+ await flushPromises ( )
396+ await vi . waitFor (
397+ ( ) => {
398+ expect ( vm . classes ( 'md-template' ) ) . toBe ( true )
399+ } ,
400+ { timeout : 1000 }
401+ )
402+ expect ( vm . find ( '.reveal .slides' ) . html ( ) ) . toMatchSnapshot ( )
403+ } )
404+
405+ it ( 'should render about us template' , async ( ) => {
406+ global . fetch = vi . fn ( ) . mockImplementation ( ( ) => {
407+ return Promise . resolve ( {
408+ text : ( ) =>
409+ Promise . resolve ( `---
410+ slide: title-content
411+ presenter: John Doe
412+ logo: https://external:9200/cat.jpg
413+ aboutUs:
414+ - title: WWW
415+ text: www.example.com
416+ - title: LINKEDIN
417+ text: www.linkedin.com/company/example
418+ - title: FACEBOOK
419+ text: www.facebook.com/example
420+ - title: X
421+ text: www.x.com/example
422+ ---
423+ # About Us ::slide:about-us
424+
425+ Some content about us.
426+ ` ) ,
427+ blob : ( ) => Promise . resolve ( new Blob ( [ ] , { type : 'text/markdown' } ) )
428+ } )
429+ } )
430+ const vm = getWrapper ( )
431+ await flushPromises ( )
432+ await vi . waitFor (
433+ ( ) => {
434+ expect ( vm . classes ( 'md-template' ) ) . toBe ( true )
435+ } ,
436+ { timeout : 1000 }
437+ )
438+ expect ( vm . find ( '.reveal .slides' ) . html ( ) ) . toMatchSnapshot ( )
439+ } )
440+ } )
0 commit comments