Skip to content

Commit fa7702c

Browse files
committed
add tests
Signed-off-by: Prajwol Amatya <prajwolamatya11@gmail.com>
1 parent eb5f5ee commit fa7702c

File tree

3 files changed

+490
-4
lines changed

3 files changed

+490
-4
lines changed

src/App.vue

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ onMounted(async () => {
121121
})
122122
123123
if (reveal.isReady()) {
124-
applyTemplateIfNeeded()
124+
await applyTemplateIfNeeded()
125125
addCustomSlideNumber()
126126
updateImageStructure()
127127
fitContent()
@@ -399,16 +399,19 @@ function setFontColor() {
399399
el.style.color = color
400400
})
401401
}
402-
function applyTemplateIfNeeded() {
402+
async function applyTemplateIfNeeded() {
403403
const [markdown, frontMatter] = separateFrontmatterAndMarkdown()
404404
loadTemplate = !!(frontMatter.metadata?.slide || markdown.match(headingSlideRegex))
405405
406406
if (loadTemplate) {
407407
// dynamically import CSS file only when needed
408408
presentationViewerRef.value.classList.add('md-template')
409-
import('./css/templates.css')
409+
await import('./css/templates.css')
410410
setFontColor()
411+
await new Promise((resolve) => requestAnimationFrame(resolve))
412+
await new Promise((resolve) => setTimeout(resolve, 100))
411413
}
414+
return loadTemplate
412415
}
413416
async function updateLogoUrl() {
414417
const frontMatter = separateFrontmatterAndMarkdown()[1]

tests/unit/App.spec.ts

Lines changed: 329 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,150 @@
11
import { shallowMount, flushPromises } from '@vue/test-utils'
22
import 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
5149
vi.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
99244
URL.createObjectURL = vi
100245
.fn()
101246
.mockImplementation(() => 'blob:nodedata:0295bafb-5976-468a-a263-685a8872cb96')
@@ -113,3 +258,186 @@ 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+
287+
global.fetch = vi.fn().mockResolvedValue(
288+
new Response(
289+
`---
290+
slide: title-content
291+
logo: [invalid yaml
292+
---
293+
`,
294+
{
295+
status: 200,
296+
headers: { 'Content-Type': 'text/markdown' }
297+
}
298+
)
299+
)
300+
301+
const vm = getWrapper()
302+
await flushPromises()
303+
304+
expect(errorSpy).toHaveBeenCalledWith(
305+
'Frontmatter parsing error:',
306+
expect.objectContaining({
307+
name: 'YAMLException',
308+
message: expect.stringContaining('unexpected end of the stream within a flow collection')
309+
})
310+
)
311+
expect(vm.html()).toContain(
312+
'YAMLException: unexpected end of the stream within a flow collection at line 3, column 1'
313+
)
314+
})
315+
316+
it('should render cover template', async () => {
317+
global.fetch = vi.fn().mockImplementation(() => {
318+
return Promise.resolve({
319+
text: () =>
320+
Promise.resolve(`---
321+
slide: title-content
322+
presenter: John Doe
323+
logo: https://external:9200/cat.jpg
324+
---
325+
# Reveal Js Templates in Web App Presentation Viewer ::slide:cover
326+
`),
327+
blob: () => Promise.resolve(new Blob([], { type: 'text/markdown' }))
328+
})
329+
})
330+
const vm = getWrapper()
331+
await flushPromises()
332+
await vi.waitFor(
333+
() => {
334+
expect(vm.classes('md-template')).toBe(true)
335+
},
336+
{ timeout: 1000 }
337+
)
338+
expect(vm.find('.reveal .slides').html()).toMatchSnapshot()
339+
})
340+
341+
it('should render title content template', async () => {
342+
global.fetch = vi.fn().mockImplementation(() => {
343+
return Promise.resolve({
344+
text: () =>
345+
Promise.resolve(`---
346+
slide: title-content
347+
presenter: John Doe
348+
logo: https://external:9200/cat.jpg
349+
---
350+
# Title Content Slide
351+
352+
- Introduction to mountain ecosystems
353+
- Basic concepts of quantum encryption
354+
- Exploring culinary traditions in Southeast Asia
355+
- Overview of blockchain consensus mechanisms
356+
- Setting up a personal productivity system
357+
- Writing clean, maintainable JavaScript code
358+
- Understanding modern art movements
359+
- History of aviation and early flight experiments
360+
- Managing team dynamics in remote work
361+
`),
362+
blob: () => Promise.resolve(new Blob([], { type: 'text/markdown' }))
363+
})
364+
})
365+
const vm = getWrapper()
366+
await flushPromises()
367+
await vi.waitFor(
368+
() => {
369+
expect(vm.classes('md-template')).toBe(true)
370+
},
371+
{ timeout: 1000 }
372+
)
373+
expect(vm.find('.reveal .slides').html()).toMatchSnapshot()
374+
})
375+
376+
it('should render title content image template', async () => {
377+
global.fetch = vi.fn().mockImplementation(() => {
378+
return Promise.resolve({
379+
text: () =>
380+
Promise.resolve(`---
381+
slide: title-content
382+
presenter: John Doe
383+
logo: https://external:9200/cat.jpg
384+
---
385+
# Title Content Image Slide ::slide:title-content-image
386+
387+
- Introduction to mountain ecosystems
388+
- Basic concepts of quantum encryption
389+
- Exploring culinary traditions in Southeast Asia
390+
- Overview of blockchain consensus mechanisms
391+
392+
![cat](https://external:9200/cat.jpg)
393+
`),
394+
blob: () => Promise.resolve(new Blob([], { type: 'text/markdown' }))
395+
})
396+
})
397+
const vm = getWrapper()
398+
await flushPromises()
399+
await vi.waitFor(
400+
() => {
401+
expect(vm.classes('md-template')).toBe(true)
402+
},
403+
{ timeout: 1000 }
404+
)
405+
expect(vm.find('.reveal .slides').html()).toMatchSnapshot()
406+
})
407+
408+
it('should render about us template', async () => {
409+
global.fetch = vi.fn().mockImplementation(() => {
410+
return Promise.resolve({
411+
text: () =>
412+
Promise.resolve(`---
413+
slide: title-content
414+
presenter: John Doe
415+
logo: https://external:9200/cat.jpg
416+
aboutUs:
417+
- title: WWW
418+
text: www.example.com
419+
- title: LINKEDIN
420+
text: www.linkedin.com/company/example
421+
- title: FACEBOOK
422+
text: www.facebook.com/example
423+
- title: X
424+
text: www.x.com/example
425+
---
426+
# About Us ::slide:about-us
427+
428+
Some content about us.
429+
`),
430+
blob: () => Promise.resolve(new Blob([], { type: 'text/markdown' }))
431+
})
432+
})
433+
const vm = getWrapper()
434+
await flushPromises()
435+
await vi.waitFor(
436+
() => {
437+
expect(vm.classes('md-template')).toBe(true)
438+
},
439+
{ timeout: 1000 }
440+
)
441+
expect(vm.find('.reveal .slides').html()).toMatchSnapshot()
442+
})
443+
})

0 commit comments

Comments
 (0)