mirror of
https://github.com/ajurna/cbwebreader.git
synced 2026-11-02 18:46:53 +00:00
- Introduced a continuous reading mode ("scroll"), allowing users to read without pagination.
- Implemented a toggle in the UI to switch between "paged" and "continuous" modes, with preferences saved in `localStorage`.
- Enhanced keyboard navigation by throttling repeated actions and ignoring irrelevant keypresses during form interactions.
- Integrated `IntersectionObserver` to handle lazy-loading of images and dynamically extending rendered content in continuous mode.
- Updated tests to validate both reading modes, ensuring proper state management and observer handling.
- Adjusted animations and transitions for smoother visual feedback in both modes, respecting users' motion-reduction preferences.
128 lines
4.1 KiB
JavaScript
128 lines
4.1 KiB
JavaScript
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
const { api, router } = vi.hoisted(() => ({
|
|
api: { get: vi.fn(), post: vi.fn() },
|
|
router: { currentRoute: { value: { query: {}, fullPath: '/account' } }, push: vi.fn() },
|
|
}))
|
|
|
|
vi.mock('@/api', () => ({ default: api }))
|
|
vi.mock('@/router', () => ({ default: router }))
|
|
|
|
import store from '@/store'
|
|
|
|
beforeEach(() => {
|
|
localStorage.clear()
|
|
store.commit('logOut')
|
|
api.get.mockReset()
|
|
api.post.mockReset()
|
|
router.push.mockReset()
|
|
})
|
|
|
|
describe('session state', () => {
|
|
it('is not authenticated when there is no stored profile', () => {
|
|
expect(store.getters.isAuthenticated).toBe(false)
|
|
expect(store.getters.is_superuser).toBe(false)
|
|
})
|
|
|
|
it('keeps the profile in localStorage so a reload is not a blank page', () => {
|
|
store.commit('updateUser', { id: 1, is_superuser: false })
|
|
|
|
expect(store.getters.isAuthenticated).toBe(true)
|
|
expect(JSON.parse(localStorage.getItem('u'))).toEqual({ id: 1, is_superuser: false })
|
|
})
|
|
|
|
it('logOut clears both the profile and localStorage', () => {
|
|
store.commit('updateUser', { id: 1, is_superuser: true })
|
|
store.commit('logOut')
|
|
|
|
expect(store.getters.isAuthenticated).toBe(false)
|
|
expect(localStorage.getItem('u')).toBeNull()
|
|
})
|
|
})
|
|
|
|
describe('reader mode', () => {
|
|
it('starts in paged mode when nothing is stored', async () => {
|
|
vi.resetModules()
|
|
const fresh = (await import('@/store')).default
|
|
|
|
expect(fresh.state.reader_mode).toBe('paged')
|
|
})
|
|
|
|
it('keeps the chosen mode in localStorage', () => {
|
|
store.commit('setReaderMode', 'scroll')
|
|
|
|
expect(store.state.reader_mode).toBe('scroll')
|
|
expect(localStorage.getItem('reader_mode')).toBe('scroll')
|
|
})
|
|
|
|
it('ignores a mode it does not recognise', () => {
|
|
store.commit('setReaderMode', 'scroll')
|
|
store.commit('setReaderMode', 'banana')
|
|
|
|
expect(store.state.reader_mode).toBe('scroll')
|
|
expect(localStorage.getItem('reader_mode')).toBe('scroll')
|
|
})
|
|
})
|
|
|
|
describe('restoreSession', () => {
|
|
it('refreshes the stored profile from the cookie session', async () => {
|
|
store.commit('updateUser', { id: 1, is_superuser: false })
|
|
api.get.mockResolvedValue({ data: { id: 1, is_superuser: true } })
|
|
|
|
await store.dispatch('restoreSession')
|
|
|
|
expect(api.get).toHaveBeenCalledWith('/api/account')
|
|
expect(store.getters.is_superuser).toBe(true)
|
|
})
|
|
|
|
it('logs out when the cookie no longer identifies the user', async () => {
|
|
store.commit('updateUser', { id: 1, is_superuser: false })
|
|
api.get.mockRejectedValue({ response: { status: 401 } })
|
|
|
|
await store.dispatch('restoreSession')
|
|
|
|
expect(store.getters.isAuthenticated).toBe(false)
|
|
expect(router.push).toHaveBeenCalledWith({ name: 'login' })
|
|
})
|
|
|
|
it('does nothing when there is no stored profile', async () => {
|
|
await store.dispatch('restoreSession')
|
|
|
|
expect(api.get).not.toHaveBeenCalled()
|
|
})
|
|
})
|
|
|
|
describe('token handling', () => {
|
|
it('rejects and logs out when the refresh cookie is no longer valid', async () => {
|
|
store.commit('updateUser', { id: 1, is_superuser: false })
|
|
api.post.mockRejectedValue({ response: { status: 401 } })
|
|
|
|
await expect(store.dispatch('refreshToken')).rejects.toBeTruthy()
|
|
|
|
expect(store.getters.isAuthenticated).toBe(false)
|
|
expect(router.push).toHaveBeenCalledWith(expect.objectContaining({ name: 'login' }))
|
|
})
|
|
|
|
it('does not call the API when there is no session', async () => {
|
|
await expect(store.dispatch('refreshToken')).rejects.toBeTruthy()
|
|
expect(api.post).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('does not blacklist a token for a session that does not exist', async () => {
|
|
await store.dispatch('logout')
|
|
|
|
expect(api.post).not.toHaveBeenCalled()
|
|
expect(store.getters.isAuthenticated).toBe(false)
|
|
})
|
|
|
|
it('blacklists the token when a session existed', async () => {
|
|
store.commit('updateUser', { id: 1, is_superuser: false })
|
|
api.post.mockResolvedValue({ data: {} })
|
|
|
|
await store.dispatch('logout')
|
|
|
|
expect(api.post).toHaveBeenCalledWith('/api/token/blacklist/', {})
|
|
expect(store.getters.isAuthenticated).toBe(false)
|
|
})
|
|
})
|