203 lines
7.2 KiB
JavaScript
203 lines
7.2 KiB
JavaScript
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
import { mount, flushPromises } from '@vue/test-utils'
|
|
import { createRouter, createWebHistory } from 'vue-router'
|
|
import axios from 'axios'
|
|
import AppNav from '../AppNav.vue'
|
|
import { useFeeds } from '../../composables/useFeeds'
|
|
|
|
vi.mock('axios')
|
|
|
|
describe('AppNav', () => {
|
|
let router
|
|
|
|
beforeEach(async () => {
|
|
localStorage.setItem('user-token', 'abc123')
|
|
localStorage.setItem('user-id', '7')
|
|
vi.clearAllMocks()
|
|
|
|
const { feeds, showMessage, message, showModal, viewMode, currentIndex, layout } = useFeeds()
|
|
feeds.value = []
|
|
showMessage.value = false
|
|
message.value = ''
|
|
showModal.value = false
|
|
viewMode.value = 'list'
|
|
currentIndex.value = 0
|
|
layout.value = 'list'
|
|
|
|
router = createRouter({
|
|
history: createWebHistory(),
|
|
routes: [
|
|
{ path: '/login', name: 'login', component: { template: '<div />' } },
|
|
{ path: '/feeds', name: 'feeds', component: { template: '<div />' } },
|
|
],
|
|
})
|
|
router.push('/feeds')
|
|
await router.isReady()
|
|
})
|
|
|
|
async function mountWithMenuOpen() {
|
|
const wrapper = mount(AppNav, { global: { plugins: [router] } })
|
|
await wrapper.find('.app-nav__hamburger').trigger('click')
|
|
await flushPromises()
|
|
return wrapper
|
|
}
|
|
|
|
it('toggles the menu open and closed via the hamburger button', async () => {
|
|
const wrapper = mount(AppNav, { global: { plugins: [router] } })
|
|
|
|
expect(wrapper.find('.app-nav__menu').exists()).toBe(false)
|
|
|
|
await wrapper.find('.app-nav__hamburger').trigger('click')
|
|
expect(wrapper.find('.app-nav__menu').exists()).toBe(true)
|
|
|
|
await wrapper.find('.app-nav__hamburger').trigger('click')
|
|
expect(wrapper.find('.app-nav__menu').exists()).toBe(false)
|
|
})
|
|
|
|
it('clears stored credentials and redirects to login on logout', async () => {
|
|
const wrapper = await mountWithMenuOpen()
|
|
|
|
await wrapper.find('.app-nav__logout').trigger('click')
|
|
await flushPromises()
|
|
|
|
expect(localStorage.getItem('user-token')).toBeNull()
|
|
expect(localStorage.getItem('user-id')).toBeNull()
|
|
expect(router.currentRoute.value.name).toBe('login')
|
|
})
|
|
|
|
it('triggers a sync from the menu', async () => {
|
|
axios.get.mockResolvedValue({ data: { feeds: [] } })
|
|
axios.post.mockResolvedValueOnce({ status: 200 })
|
|
|
|
const wrapper = await mountWithMenuOpen()
|
|
|
|
const syncButton = wrapper.findAll('.app-nav__menu-item').find(el => el.text() === 'Sync')
|
|
await syncButton.trigger('click')
|
|
await flushPromises()
|
|
|
|
expect(axios.post).toHaveBeenCalledWith(
|
|
'/api/v1/article/sync',
|
|
{ user_id: 7 },
|
|
expect.anything(),
|
|
)
|
|
// Menu auto-closes after an action
|
|
expect(wrapper.find('.app-nav__menu').exists()).toBe(false)
|
|
})
|
|
|
|
it('opens the add-feed modal from the menu', async () => {
|
|
const wrapper = await mountWithMenuOpen()
|
|
const { showModal } = useFeeds()
|
|
|
|
const addButton = wrapper.findAll('.app-nav__menu-item').find(el => el.text() === 'Add RSS')
|
|
await addButton.trigger('click')
|
|
|
|
expect(showModal.value).toBe(true)
|
|
})
|
|
|
|
it('switches the view mode from the menu and closes it', async () => {
|
|
const wrapper = await mountWithMenuOpen()
|
|
const { viewMode } = useFeeds()
|
|
|
|
const viewButton = wrapper.findAll('.app-nav__menu-item').find(el => el.text() === 'Article view')
|
|
await viewButton.trigger('click')
|
|
|
|
expect(viewMode.value).toBe('article')
|
|
expect(wrapper.find('.app-nav__menu').exists()).toBe(false)
|
|
})
|
|
|
|
it('switches the list layout from the menu and closes it', async () => {
|
|
const wrapper = await mountWithMenuOpen()
|
|
const { layout } = useFeeds()
|
|
|
|
const layoutButton = wrapper.findAll('.app-nav__menu-item').find(el => el.text() === 'Card layout')
|
|
await layoutButton.trigger('click')
|
|
|
|
expect(layout.value).toBe('cards')
|
|
expect(wrapper.find('.app-nav__menu').exists()).toBe(false)
|
|
})
|
|
|
|
it('hides the layout toggle while in article view', async () => {
|
|
const { viewMode } = useFeeds()
|
|
viewMode.value = 'article'
|
|
|
|
const wrapper = await mountWithMenuOpen()
|
|
|
|
expect(wrapper.findAll('.app-nav__menu-item').find(el => el.text().includes('layout'))).toBeUndefined()
|
|
})
|
|
|
|
it('marks all articles as read from the menu after confirmation', async () => {
|
|
const { feeds } = useFeeds()
|
|
feeds.value = [
|
|
{ id: 1, title: 'Article one', content: '', url: 'https://example.test/1', timestamp: '2026-01-01' },
|
|
{ id: 2, title: 'Article two', content: '', url: 'https://example.test/2', timestamp: '2026-01-02' },
|
|
]
|
|
axios.put.mockResolvedValue({ status: 200 })
|
|
const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(true)
|
|
|
|
const wrapper = await mountWithMenuOpen()
|
|
const markAllButton = wrapper.findAll('.app-nav__menu-item').find(el => el.text() === 'Mark all as read')
|
|
await markAllButton.trigger('click')
|
|
await flushPromises()
|
|
|
|
expect(confirmSpy).toHaveBeenCalled()
|
|
expect(axios.put).toHaveBeenCalledWith('/api/v1/article/read/1', null, expect.anything())
|
|
expect(axios.put).toHaveBeenCalledWith('/api/v1/article/read/2', null, expect.anything())
|
|
expect(feeds.value).toHaveLength(0)
|
|
expect(wrapper.find('.app-nav__menu').exists()).toBe(false)
|
|
|
|
confirmSpy.mockRestore()
|
|
})
|
|
|
|
it('shows the unread count in the title when there are articles', async () => {
|
|
const { feeds } = useFeeds()
|
|
feeds.value = [
|
|
{ id: 1, title: 'Article one', content: '', url: 'https://example.test/1', timestamp: '2026-01-01' },
|
|
{ id: 2, title: 'Article two', content: '', url: 'https://example.test/2', timestamp: '2026-01-02' },
|
|
]
|
|
|
|
const wrapper = mount(AppNav, { global: { plugins: [router] } })
|
|
await flushPromises()
|
|
|
|
expect(wrapper.find('.app-nav__title').text()).toContain('(2)')
|
|
})
|
|
|
|
it('excludes already-read articles from the counter while in article view', async () => {
|
|
const { feeds } = useFeeds()
|
|
feeds.value = [
|
|
{ id: 1, title: 'Article one', read: true, content: '', url: 'https://example.test/1', timestamp: '2026-01-01' },
|
|
{ id: 2, title: 'Article two', read: false, content: '', url: 'https://example.test/2', timestamp: '2026-01-02' },
|
|
]
|
|
|
|
const wrapper = mount(AppNav, { global: { plugins: [router] } })
|
|
await flushPromises()
|
|
|
|
expect(wrapper.find('.app-nav__title').text()).toContain('(1)')
|
|
})
|
|
|
|
it('hides the unread count when there are no articles', async () => {
|
|
const wrapper = mount(AppNav, { global: { plugins: [router] } })
|
|
await flushPromises()
|
|
|
|
expect(wrapper.find('.app-nav__unread').exists()).toBe(false)
|
|
})
|
|
|
|
it('does not mark articles as read when the confirmation is dismissed', async () => {
|
|
const { feeds } = useFeeds()
|
|
feeds.value = [
|
|
{ id: 1, title: 'Article one', content: '', url: 'https://example.test/1', timestamp: '2026-01-01' },
|
|
]
|
|
const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(false)
|
|
|
|
const wrapper = await mountWithMenuOpen()
|
|
const markAllButton = wrapper.findAll('.app-nav__menu-item').find(el => el.text() === 'Mark all as read')
|
|
await markAllButton.trigger('click')
|
|
await flushPromises()
|
|
|
|
expect(confirmSpy).toHaveBeenCalled()
|
|
expect(axios.put).not.toHaveBeenCalled()
|
|
expect(feeds.value).toHaveLength(1)
|
|
|
|
confirmSpy.mockRestore()
|
|
})
|
|
})
|