card view, minor css bugfixes

This commit is contained in:
2026-06-08 14:22:04 +02:00
parent 3c42ebb972
commit a98a8ba9e6
6 changed files with 275 additions and 17 deletions
+122 -1
View File
@@ -23,13 +23,14 @@ describe('RssFeeds', () => {
// useFeeds() returns module-level singleton refs shared across the whole
// app (and this spec file) — reset them so state doesn't leak between tests.
const { feeds, showMessage, message, showModal, viewMode, currentIndex } = useFeeds()
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'
})
it('fetches the current user articles and shows the empty state', async () => {
@@ -70,6 +71,72 @@ describe('RssFeeds', () => {
expect(wrapper.text()).not.toContain('No unread articles.')
})
it('renders the list as cards when the card layout is selected', async () => {
axios.get.mockResolvedValueOnce({
data: {
feeds: [
{
title: 'My Feed',
items: [
{
id: 1,
title: 'Article one',
content: '<p>hello</p>',
url: 'https://example.test/1',
timestamp: '2026-01-01',
},
],
},
],
},
})
const { layout } = useFeeds()
layout.value = 'cards'
const wrapper = mount(RssFeeds)
await flushPromises()
expect(wrapper.find('.article').classes()).toContain('article--cards')
})
it('lets a card grow to fit the full article once its readable content has loaded', async () => {
axios.get.mockResolvedValueOnce({
data: {
feeds: [
{
title: 'My Feed',
items: [
{
id: 1,
title: 'Article one',
content: '<p>short summary</p>',
url: 'https://example.test/1',
timestamp: '2026-01-01',
},
],
},
],
},
})
axios.post.mockResolvedValueOnce({ data: { content: '<html><body><article><p>full text</p></article></body></html>' } })
const { layout } = useFeeds()
layout.value = 'cards'
const wrapper = mount(RssFeeds)
await flushPromises()
// Clamped to a fixed number of lines while only the short summary is shown...
expect(wrapper.find('.feed-content').classes()).toContain('feed-content--clamped')
await wrapper.find('.feed-title').trigger('click')
await flushPromises()
// ...but allowed to grow once the user has loaded the full readable article.
expect(wrapper.find('.feed-content').classes()).not.toContain('feed-content--clamped')
})
it('sorts articles by date across feeds, newest first', async () => {
axios.get.mockResolvedValueOnce({
data: {
@@ -209,4 +276,58 @@ describe('RssFeeds', () => {
expect(wrapper.find('.article-single .feed-title').text()).toBe('Article one')
})
it('drops articles read while paging through article view once back in the list', async () => {
axios.get.mockResolvedValueOnce({
data: {
feeds: [
{
title: 'My Feed',
items: [
{
id: 1,
title: 'Article one',
content: '<p>one</p>',
url: 'https://example.test/1',
timestamp: '2026-03-01 10:00:00',
},
{
id: 2,
title: 'Article two',
content: '<p>two</p>',
url: 'https://example.test/2',
timestamp: '2026-02-01 10:00:00',
},
{
id: 3,
title: 'Article three',
content: '<p>three</p>',
url: 'https://example.test/3',
timestamp: '2026-01-01 10:00:00',
},
],
},
],
},
})
axios.put.mockResolvedValue({ status: 200 })
const wrapper = mount(RssFeeds)
await flushPromises()
const { toggleViewMode, leaveArticleView } = useFeeds()
// Enter article view (marks "Article one" read), page forward to "Article
// two" (marks it read too), then leave without visiting "Article three".
toggleViewMode()
await flushPromises()
await wrapper.findAll('.article-nav__btn')[1].trigger('click')
await flushPromises()
leaveArticleView()
await flushPromises()
const titles = wrapper.findAll('.feed-title').map(el => el.text())
expect(titles).toEqual(['Article three'])
})
})