Skip to content

Commit 3981a8c

Browse files
lcharetteCopilot
andcommitted
Add frontend coverage
Co-authored-by: Copilot <copilot@github.com>
1 parent 085a211 commit 3981a8c

3 files changed

Lines changed: 161 additions & 4 deletions

File tree

.github/workflows/Build.yml

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,10 @@ jobs:
3939
4040
- name: Upload coverage to Codecov
4141
if: github.event_name != 'schedule'
42-
uses: codecov/codecov-action@v4
42+
uses: codecov/codecov-action@v5
4343
with:
4444
token: ${{ secrets.CODECOV_TOKEN }}
45-
file: ./_meta/coverage.xml
45+
files: ./_meta/coverage.xml
4646
fail_ci_if_error: true
4747

4848
- name: Upload test results to Codecov
@@ -99,6 +99,37 @@ jobs:
9999
- name: Test
100100
run: npm run vite:build
101101

102+
Vitest:
103+
104+
strategy:
105+
fail-fast: false
106+
matrix:
107+
node_versions: [24]
108+
109+
runs-on: ubuntu-latest
110+
name: Vitest - Node ${{ matrix.node_versions }}
111+
112+
steps:
113+
- uses: actions/checkout@v4
114+
115+
- uses: actions/setup-node@v4
116+
with:
117+
node-version: ${{ matrix.node_versions }}
118+
119+
- name: Install Dependencies
120+
run: npm ci
121+
122+
- name: Run Tests with Coverage
123+
run: npm run coverage
124+
125+
- name: Upload coverage to Codecov
126+
if: github.event_name != 'schedule'
127+
uses: codecov/codecov-action@v5
128+
with:
129+
token: ${{ secrets.CODECOV_TOKEN }}
130+
files: ./_meta/frontend-coverage/lcov.info
131+
fail_ci_if_error: true
132+
102133
Prettier:
103134
strategy:
104135
fail-fast: false
@@ -142,7 +173,7 @@ jobs:
142173
Deploy:
143174
name: Deploy code to prod
144175
runs-on: ubuntu-latest
145-
needs: [PHPUnit, PHPStan, Vite, Prettier, PHP-CS-Fixer]
176+
needs: [PHPUnit, PHPStan, Vite, Vitest, Prettier, PHP-CS-Fixer]
146177
if: github.event_name == 'push'
147178

148179
steps:

app/assets/SearchComponent.spec.ts

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { describe, it, expect, vi, beforeEach } from 'vitest'
2+
import { mount } from '@vue/test-utils'
3+
import axios from 'axios'
4+
import SearchComponent from './SearchComponent.vue'
5+
6+
vi.mock('axios')
7+
8+
describe('SearchComponent', () => {
9+
beforeEach(() => {
10+
vi.resetAllMocks()
11+
})
12+
13+
it('renders the search trigger input', () => {
14+
const wrapper = mount(SearchComponent)
15+
const inputs = wrapper.findAll('input[type="text"]')
16+
expect(inputs.length).toBeGreaterThan(0)
17+
})
18+
19+
it('does not show results or error on initial render', () => {
20+
const wrapper = mount(SearchComponent)
21+
expect(wrapper.find('[uk-spinner]').exists()).toBe(false)
22+
expect(wrapper.find('.uk-alert-danger').exists()).toBe(false)
23+
expect(wrapper.find('.uk-list').exists()).toBe(false)
24+
})
25+
26+
it('does not fetch when query is shorter than minimum length', async () => {
27+
const axiosGet = vi.mocked(axios.get)
28+
const wrapper = mount(SearchComponent)
29+
30+
const searchInput = wrapper.find('input[autofocus]')
31+
await searchInput.setValue('ab')
32+
await wrapper.vm.$nextTick()
33+
34+
expect(axiosGet).not.toHaveBeenCalled()
35+
})
36+
37+
it('fetches results when query meets minimum length', async () => {
38+
const mockData = {
39+
count: 1,
40+
size: 10,
41+
page: 1,
42+
rows: [
43+
{
44+
title: 'Test Page',
45+
slug: 'test-page',
46+
route: '/6.0/test-page',
47+
snippet: 'A test snippet',
48+
score: 1.0,
49+
version: '6.0'
50+
}
51+
]
52+
}
53+
vi.mocked(axios.get).mockResolvedValue({ data: mockData })
54+
55+
const wrapper = mount(SearchComponent)
56+
const searchInput = wrapper.find('input[autofocus]')
57+
await searchInput.setValue('test')
58+
59+
// Flush the watcher and async call
60+
await new Promise((resolve) => setTimeout(resolve, 0))
61+
await wrapper.vm.$nextTick()
62+
63+
expect(axios.get).toHaveBeenCalledWith('/api/search', { params: { q: 'test' } })
64+
})
65+
66+
it('shows no results message when query is long enough but no results returned', async () => {
67+
const mockData = { count: 0, size: 10, page: 1, rows: [] }
68+
vi.mocked(axios.get).mockResolvedValue({ data: mockData })
69+
70+
const wrapper = mount(SearchComponent)
71+
const searchInput = wrapper.find('input[autofocus]')
72+
await searchInput.setValue('xyz')
73+
74+
await new Promise((resolve) => setTimeout(resolve, 0))
75+
await wrapper.vm.$nextTick()
76+
77+
expect(wrapper.text()).toContain('No results found')
78+
})
79+
80+
it('shows results list when results are returned', async () => {
81+
const mockData = {
82+
count: 2,
83+
size: 10,
84+
page: 1,
85+
rows: [
86+
{
87+
title: 'Getting Started',
88+
slug: 'getting-started',
89+
route: '/6.0/getting-started',
90+
snippet: 'Learn how to get started',
91+
score: 0.9,
92+
version: '6.0'
93+
},
94+
{
95+
title: 'Installation',
96+
slug: 'installation',
97+
route: '/6.0/installation',
98+
snippet: 'Install UserFrosting',
99+
score: 0.8,
100+
version: '6.0'
101+
}
102+
]
103+
}
104+
vi.mocked(axios.get).mockResolvedValue({ data: mockData })
105+
106+
const wrapper = mount(SearchComponent)
107+
const searchInput = wrapper.find('input[autofocus]')
108+
await searchInput.setValue('start')
109+
110+
await new Promise((resolve) => setTimeout(resolve, 0))
111+
await wrapper.vm.$nextTick()
112+
113+
const listItems = wrapper.findAll('li')
114+
expect(listItems.length).toBe(2)
115+
expect(wrapper.text()).toContain('Getting Started')
116+
expect(wrapper.text()).toContain('Installation')
117+
})
118+
})

vite.config.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { defineConfig } from 'vite'
1+
import { defineConfig } from 'vitest/config'
22
import vue from '@vitejs/plugin-vue'
33

44
// Get vite port from env, default to 3000
@@ -45,5 +45,13 @@ export default defineConfig({
4545
// not to prebundle them.
4646
optimizeDeps: {
4747
include: ['uikit', 'uikit/dist/js/uikit-icons']
48+
},
49+
test: {
50+
environment: 'happy-dom',
51+
coverage: {
52+
provider: 'v8',
53+
reporter: ['text', 'json', 'html', 'lcov'],
54+
reportsDirectory: '../../_meta/frontend-coverage'
55+
}
4856
}
4957
})

0 commit comments

Comments
 (0)