Repository: liqd/a4-meinberlin

Issue:
Not enough padding between (down-)votes and support icons
**URL:** https://meinberlin-dev.liqd.net/projekte/budgeting-1/?mode=list#proposal_988
**user:** admin
**expected behaviour:** uniform space between icons
**behaviour:** not enough space between down votes and support icon
**important screensize:**
**device & browser:** firefox
**Comment/Question:** 

Screenshot?

![mb-budgeting-icons](https://user-images.githubusercontent.com/1710203/216310800-1d8e95fe-38a8-415f-a1b0-3c3003b55972.png)



Discussion:
linked https://github.com/liqd/a4-meinberlin/issues/4719

Resolving patch:
diff --git a/meinberlin/apps/budgeting/api.py b/meinberlin/apps/budgeting/api.py
--- a/meinberlin/apps/budgeting/api.py
+++ b/meinberlin/apps/budgeting/api.py
@@ -314,6 +314,7 @@ def get_queryset(self):
             Proposal.objects.filter(module=self.module)
             .annotate_comment_count()
             .annotate_positive_rating_count()
+            .annotate_negative_rating_count()
             .annotate_reference_number()
             .annotate_token_vote_count()
             .order_by("-created")


Test patch:
diff --git a/meinberlin/apps/budgeting/assets/__tests__/BudgetingProposalList.jest.jsx b/meinberlin/apps/budgeting/assets/__tests__/BudgetingProposalList.jest.jsx
--- a/meinberlin/apps/budgeting/assets/__tests__/BudgetingProposalList.jest.jsx
+++ b/meinberlin/apps/budgeting/assets/__tests__/BudgetingProposalList.jest.jsx
@@ -3,14 +3,25 @@ import { render, act, screen } from '@testing-library/react'
 import { BudgetingProposalList } from '../BudgetingProposalList'
 import { BrowserRouter } from 'react-router-dom'
 
+// permissions for anonymous/logged-in user during collection phase of 1 and 2 phase (3 phase is dfferent see ListItem test)
 const permissions = {
   view_support_count: false,
   view_rate_count: true,
   view_comment_count: true,
+  view_vote_count: false,
   has_voting_permission_and_valid_token: false
 }
 
-test('Budgeting Proposal List without list item (empty)', async () => {
+// permissions for anonymous/logged-in user with token during vote phase
+const permissionsVote = {
+  view_support_count: false,
+  view_rate_count: false,
+  view_comment_count: true,
+  view_vote_count: false,
+  has_voting_permission_and_valid_token: true
+}
+
+test('Budgeting Proposal List (1, 2, 3) - no ideas - check: empty list string', async () => {
   // mimicking fetch response with empty list
   const mockedFetchEmpty = Promise.resolve({
     json: () => Promise.resolve({ results: [], permissions })
@@ -25,55 +36,104 @@ test('Budgeting Proposal List without list item (empty)', async () => {
   // waiting for async fetching ends --> without this, the
   // act(...) console.error will appear
   await act(() => mockedFetchEmpty)
-  const emptyList = screen.queryAllByText('mock text')
-  expect(emptyList).toBeTruthy()
+  expect(screen.getByText('Nothing to show')).toBeTruthy()
 
   // reverse overwrite of global.fetch
   await global.fetch.mockClear()
 })
 
-test('Budgeting Proposal List with one list item', async () => {
-  // sample data (one proposal item)
-  const mockedResults = [
-    {
-      comment_count: 0,
-      created: '2021-11-11T15:36:57.941072+01:00',
-      creator: 'admin',
-      is_archived: false,
-      name: 'erster vorschlag',
-      negative_rating_count: 0,
-      pk: 7,
-      positive_rating_count: 0,
-      url: '/budgeting/2021-00007/',
-      additional_item_badges_for_list_count: 1,
-      item_badges_for_list: [
-        ['moderator_status', 'wird ueberprueft', 'CONSIDERATION'],
-        ['budget', '20€'], ['category', 'candalf']]
-    }
-  ]
-
-  // mimicking fetch response
-  const mockedFetch = Promise.resolve({
-    json: () => Promise.resolve({ results: mockedResults, permissions })
+test('Budgeting Proposal List (3) - voting, finished phase no token - all user - check: no vote count or link',
+  async () => {
+    // sample data (one proposal item)
+    const mockedResults = [
+      {
+        comment_count: 6,
+        created: '2021-11-11T15:36:57.941072+01:00',
+        creator: 'admin',
+        is_archived: false,
+        name: 'erster vorschlag',
+        negative_rating_count: 1,
+        pk: 7,
+        positive_rating_count: 3,
+        url: '/budgeting/2021-00007/',
+        additional_item_badges_for_list_count: 1,
+        item_badges_for_list: [
+          ['moderator_status', 'wird ueberprueft', 'CONSIDERATION'],
+          ['budget', '20€'], ['category', 'candalf']]
+      }
+    ]
+
+    // mimicking fetch response
+    const mockedFetch = Promise.resolve({
+      json: () => Promise.resolve({ results: mockedResults, permissions })
+    })
+
+    // overwrite global.fetch with mock function
+    global.fetch = jest.fn().mockImplementation(() => mockedFetch)
+
+    render(<BrowserRouter><BudgetingProposalList /></BrowserRouter>)
+    expect(global.fetch).toHaveBeenCalledTimes(1)
+
+    // waiting for async fetching ends --> without this, the
+    // act(...) console.error will appear
+    await act(() => mockedFetch)
+    expect(screen.getByText('erster vorschlag')).toBeTruthy()
+    expect(screen.queryByText('you have 1 vote left.you have %s votes left.')).toBeNull()
+    expect(screen.queryByDisplayValue('End session')).toBeNull()
+
+    // reverse overwrite of global.fetch
+    await global.fetch.mockClear()
   })
 
-  // overwrite global.fetch with mock function
-  global.fetch = jest.fn().mockImplementation(() => mockedFetch)
+test('Budgeting Proposal List (3) - voting phase - user with token 2 votes - check: vote count + end session link',
+  async () => {
+    // sample data (one proposal item)
+    const mockedResults = [
+      {
+        comment_count: 6,
+        created: '2021-11-11T15:36:57.941072+01:00',
+        creator: 'admin',
+        is_archived: false,
+        name: 'erster vorschlag',
+        negative_rating_count: 1,
+        pk: 7,
+        positive_rating_count: 3,
+        url: '/budgeting/2021-00007/',
+        additional_item_badges_for_list_count: 1,
+        item_badges_for_list: [
+          ['moderator_status', 'wird ueberprueft', 'CONSIDERATION'],
+          ['budget', '20€'], ['category', 'candalf']]
+      }
+    ]
 
-  render(<BrowserRouter><BudgetingProposalList /></BrowserRouter>)
-  expect(global.fetch).toHaveBeenCalledTimes(1)
+    // mimicking fetch response with met info for token
+    const mockedFetch = Promise.resolve({
+      json: () => Promise.resolve({
+        results: mockedResults,
+        permissions: permissionsVote,
+        token_info: { num_votes_left: 2, votes_left: true }
+      })
+    })
 
-  // waiting for async fetching ends --> without this, the
-  // act(...) console.error will appear
-  await act(() => mockedFetch)
-  const listItem = screen.queryByText('erster vorschlag')
-  expect(listItem).toBeTruthy()
+    // overwrite global.fetch with mock function
+    global.fetch = jest.fn().mockImplementation(() => mockedFetch)
 
-  // reverse overwrite of global.fetch
-  await global.fetch.mockClear()
-})
+    render(<BrowserRouter><BudgetingProposalList /></BrowserRouter>)
+    expect(global.fetch).toHaveBeenCalledTimes(1)
+
+    // waiting for async fetching ends --> without this, the
+    // act(...) console.error will appear
+    await act(() => mockedFetch)
+    expect(screen.getByText('erster vorschlag')).toBeTruthy()
+    // FIXME figure out how to deal with plurals
+    expect(screen.getByText('you have 1 vote left.you have %s votes left.2')).toBeTruthy()
+    expect(screen.getByText('End session', { selector: 'button' })).toBeTruthy()
+
+    // reverse overwrite of global.fetch
+    await global.fetch.mockClear()
+  })
 
-test('Budgeting Proposal List with fetch error', async () => {
+test('Budgeting Proposal List (1, 2, 3) - all users - check: fetch list item error', async () => {
   // overwrite global.fetch with mock function
   global.fetch = jest.fn().mockRejectedValue('testing: expected network error')
 
diff --git a/meinberlin/apps/budgeting/assets/__tests__/BudgetingProposalListItem.jest.jsx b/meinberlin/apps/budgeting/assets/__tests__/BudgetingProposalListItem.jest.jsx
--- a/meinberlin/apps/budgeting/assets/__tests__/BudgetingProposalListItem.jest.jsx
+++ b/meinberlin/apps/budgeting/assets/__tests__/BudgetingProposalListItem.jest.jsx
@@ -2,13 +2,183 @@ import React from 'react'
 import { render, screen } from '@testing-library/react'
 import { BudgetingProposalListItem } from '../BudgetingProposalListItem'
 
-const permissions = {
-  view_support_count: false,
-  view_rate_count: true,
-  view_comment_count: true
-}
+test('Budgeting Proposal ListItem (1,2) - collection phase - initiator, mod, logged-in, anonymous users (admin sees all stats in all phases)', () => {
+  const permissions = {
+    view_support_count: false,
+    view_rate_count: true,
+    view_comment_count: true,
+    vote_allowed: false,
+    has_voting_permission_and_valid_token: false
+  }
+
+  const proposal = {
+    name: 'myProposal',
+    url: 'www',
+    creator: 'creator',
+    created: '2021-11-11T15:37:19.490201+01:00',
+    additional_item_badges_for_list_count: 0,
+    item_badges_for_list: [
+      ['moderator_status', 'wird ueberprueft', 'CONSIDERATION'],
+      ['budget', '20€'], ['category', 'candalf']],
+    reference_number: '2021-12345',
+    positive_rating_count: 2,
+    negative_rating_count: 1,
+    support_count: 8,
+    vote_count: 1,
+    comment_count: 7,
+    pk: 7,
+    vote_allowed: false
+  }
+  render(<BudgetingProposalListItem proposal={proposal} permissions={permissions} />)
+  expect(screen.getByText('myProposal')).toBeTruthy()
+  expect(screen.getByText('creator')).toBeTruthy()
+  // support string renders multiple strings due to plurals so using title
+  expect(screen.queryByTitle('Support')).toBeNull()
+  expect(screen.queryByText('Total votes')).toBeNull()
+  expect(screen.getByText('Comments')).toBeTruthy()
+  expect(screen.getByText('Positive Ratings')).toBeTruthy()
+  expect(screen.getByText('Negative Ratings')).toBeTruthy()
+  const resolvedDate =
+    screen.queryByText('created on 11. November 2021 - 2021-12345') ||
+    screen.queryByText('updated on 11 November 2021 - 2021-12345')
+  expect(resolvedDate).toBeTruthy()
+})
+
+test('Budgeting Proposal ListItem (3) - collection phase - initiator, mod, logged-in, anonymous users', () => {
+  const permissions = {
+    view_support_count: false,
+    view_rate_count: false,
+    view_comment_count: true,
+    vote_allowed: false,
+    has_voting_permission_and_valid_token: false
+  }
+
+  const proposal = {
+    name: 'myProposal',
+    url: 'www',
+    creator: 'creator',
+    created: '2021-11-11T15:37:19.490201+01:00',
+    additional_item_badges_for_list_count: 0,
+    item_badges_for_list: [
+      ['moderator_status', 'wird ueberprueft', 'CONSIDERATION'],
+      ['budget', '20€'], ['category', 'candalf']],
+    reference_number: '2021-12345',
+    positive_rating_count: 2,
+    negative_rating_count: 1,
+    support_count: 8,
+    vote_count: 1,
+    comment_count: 7,
+    pk: 7,
+    vote_allowed: false
+  }
+  render(<BudgetingProposalListItem proposal={proposal} permissions={permissions} />)
+  expect(screen.getByText('myProposal')).toBeTruthy()
+  expect(screen.getByText('creator')).toBeTruthy()
+  expect(screen.queryByTitle('Support')).toBeNull()
+  expect(screen.queryByText('Total votes')).toBeNull()
+  expect(screen.getByText('Comments')).toBeTruthy()
+  expect(screen.queryByText('Positive Ratings')).toBeNull()
+  expect(screen.queryByText('Negative Ratings')).toBeNull()
+  const resolvedDate =
+    screen.queryByText('created on 11. November 2021 - 2021-12345') ||
+    screen.queryByText('updated on 11 November 2021 - 2021-12345')
+  expect(resolvedDate).toBeTruthy()
+})
+
+test('Budgeting Proposal ListItem (3) - support phase and between support and vote phase - initiator, mod, logged-in, anonymous users', () => {
+  const permissions = {
+    view_support_count: true,
+    view_rate_count: false,
+    view_comment_count: true,
+    vote_allowed: false,
+    has_voting_permission_and_valid_token: false
+  }
+
+  const proposal = {
+    name: 'myProposal',
+    url: 'www',
+    creator: 'creator',
+    created: '2021-11-11T15:37:19.490201+01:00',
+    additional_item_badges_for_list_count: 0,
+    item_badges_for_list: [
+      ['moderator_status', 'wird ueberprueft', 'CONSIDERATION'],
+      ['budget', '20€'], ['category', 'candalf']],
+    reference_number: '2021-12345',
+    positive_rating_count: 2,
+    negative_rating_count: 1,
+    support_count: 8,
+    vote_count: 1,
+    comment_count: 7,
+    pk: 7,
+    vote_allowed: false
+  }
+  render(<BudgetingProposalListItem proposal={proposal} permissions={permissions} />)
+  expect(screen.getByText('myProposal')).toBeTruthy()
+  expect(screen.getByText('creator')).toBeTruthy()
+  expect(screen.getByTitle('Support')).toBeTruthy()
+  expect(screen.queryByText('Total votes')).toBeNull()
+  expect(screen.getByText('Comments')).toBeTruthy()
+  expect(screen.queryByText('Positive Ratings')).toBeNull()
+  expect(screen.queryByText('Negative Ratings')).toBeNull()
+  const resolvedDate =
+    screen.queryByText('created on 11. November 2021 - 2021-12345') ||
+    screen.queryByText('updated on 11 November 2021 - 2021-12345')
+  expect(resolvedDate).toBeTruthy()
+})
+
+test('Budgeting Proposal ListItem (3) - vote phase with token - logged-in, anonymous', () => {
+  const permissions = {
+    view_support_count: false,
+    view_rate_count: false,
+    view_comment_count: true,
+    view_vote_count: false,
+    vote_allowed: true,
+    has_voting_permission_and_valid_token: true
+  }
+
+  const proposal = {
+    name: 'myProposal',
+    url: 'www',
+    creator: 'creator',
+    created: '2021-11-11T15:37:19.490201+01:00',
+    additional_item_badges_for_list_count: 0,
+    item_badges_for_list: [
+      ['moderator_status', 'wird ueberprueft', 'CONSIDERATION'],
+      ['budget', '20€'], ['category', 'candalf']],
+    reference_number: '2021-12345',
+    positive_rating_count: 2,
+    negative_rating_count: 1,
+    support_count: 8,
+    vote_count: 1,
+    comment_count: 7,
+    pk: 7,
+    vote_allowed: true
+  }
+  render(<BudgetingProposalListItem proposal={proposal} permissions={permissions} />)
+  expect(screen.getByText('myProposal')).toBeTruthy()
+  expect(screen.getByText('creator')).toBeTruthy()
+  expect(screen.queryByTitle('Support')).toBeNull()
+  expect(screen.queryByText('Total votes')).toBeNull()
+  expect(screen.getByText('Comments')).toBeTruthy()
+  expect(screen.queryByText('Positive Ratings')).toBeNull()
+  expect(screen.queryByText('Negative Ratings')).toBeNull()
+  expect(screen.getByText('Give my vote')).toBeTruthy()
+  const resolvedDate =
+    screen.queryByText('created on 11. November 2021 - 2021-12345') ||
+    screen.queryByText('updated on 11 November 2021 - 2021-12345')
+  expect(resolvedDate).toBeTruthy()
+})
+
+test('Budgeting Proposal ListItem (3) - vote phase with token - initiator, moderator', () => {
+  const permissions = {
+    view_support_count: true,
+    view_rate_count: false,
+    view_comment_count: true,
+    view_vote_count: false,
+    vote_allowed: true,
+    has_voting_permission_and_valid_token: true
+  }
 
-test('render list item with vote button', () => {
   const proposal = {
     name: 'myProposal',
     url: 'www',
@@ -18,18 +188,40 @@ test('render list item with vote button', () => {
     item_badges_for_list: [
       ['moderator_status', 'wird ueberprueft', 'CONSIDERATION'],
       ['budget', '20€'], ['category', 'candalf']],
-    reference_number: '2021-12345'
+    reference_number: '2021-12345',
+    positive_rating_count: 2,
+    negative_rating_count: 1,
+    support_count: 8,
+    vote_count: 1,
+    comment_count: 7,
+    pk: 7,
+    vote_allowed: true
   }
   render(<BudgetingProposalListItem proposal={proposal} permissions={permissions} />)
   expect(screen.getByText('myProposal')).toBeTruthy()
   expect(screen.getByText('creator')).toBeTruthy()
+  expect(screen.getByTitle('Support')).toBeTruthy()
+  expect(screen.queryByText('Total votes')).toBeNull()
+  expect(screen.getByText('Comments')).toBeTruthy()
+  expect(screen.queryByText('Positive Ratings')).toBeNull()
+  expect(screen.queryByText('Negative Ratings')).toBeNull()
+  expect(screen.getByText('Give my vote')).toBeTruthy()
   const resolvedDate =
     screen.queryByText('created on 11. November 2021 - 2021-12345') ||
     screen.queryByText('updated on 11 November 2021 - 2021-12345')
   expect(resolvedDate).toBeTruthy()
 })
 
-test('render list item with stats', () => {
+test('Budgeting Proposal ListItem (3) - finished - logged-in, anonymous', () => {
+  const permissions = {
+    view_support_count: false,
+    view_rate_count: false,
+    view_comment_count: true,
+    view_vote_count: true,
+    vote_allowed: false,
+    has_voting_permission_and_valid_token: false
+  }
+
   const proposal = {
     name: 'myProposal',
     url: 'www',
@@ -39,13 +231,24 @@ test('render list item with stats', () => {
     item_badges_for_list: [
       ['moderator_status', 'wird ueberprueft', 'CONSIDERATION'],
       ['budget', '20€'], ['category', 'candalf']],
-    reference_number: '2021-12345'
+    reference_number: '2021-12345',
+    positive_rating_count: 2,
+    negative_rating_count: 1,
+    support_count: 8,
+    vote_count: 1,
+    comment_count: 7,
+    pk: 7,
+    vote_allowed: true
   }
-  render(
-    <BudgetingProposalListItem proposal={proposal} permissions={permissions} />
-  )
+  render(<BudgetingProposalListItem proposal={proposal} permissions={permissions} />)
   expect(screen.getByText('myProposal')).toBeTruthy()
   expect(screen.getByText('creator')).toBeTruthy()
+  expect(screen.queryByTitle('Support')).toBeNull()
+  expect(screen.getByText('Total votes')).toBeTruthy()
+  expect(screen.getByText('Comments')).toBeTruthy()
+  expect(screen.queryByText('Positive Ratings')).toBeNull()
+  expect(screen.queryByText('Negative Ratings')).toBeNull()
+  expect(screen.queryByText('Give my vote')).toBeNull()
   const resolvedDate =
     screen.queryByText('created on 11. November 2021 - 2021-12345') ||
     screen.queryByText('updated on 11 November 2021 - 2021-12345')
diff --git a/meinberlin/apps/budgeting/assets/__tests__/ListItemStats.jest.jsx b/meinberlin/apps/budgeting/assets/__tests__/ListItemStats.jest.jsx
--- a/meinberlin/apps/budgeting/assets/__tests__/ListItemStats.jest.jsx
+++ b/meinberlin/apps/budgeting/assets/__tests__/ListItemStats.jest.jsx
@@ -2,11 +2,12 @@ import React from 'react'
 import { render, screen } from '@testing-library/react'
 import { ListItemStats } from '../ListItemStats'
 
-test('rating displaying all 3 stats', () => {
+test('2 phase: rating phase - can view comments and ratings count', () => {
   const permissions = {
     view_support_count: false,
     view_rate_count: true,
-    view_comment_count: true
+    view_comment_count: true,
+    view_vote_count: false
   }
 
   render(
@@ -15,6 +16,7 @@ test('rating displaying all 3 stats', () => {
       positiveCount="4"
       negativeCount="1"
       commentCount="18"
+      voteCount="7"
     />
   )
   const pCount = screen.getByText('4')
@@ -23,13 +25,16 @@ test('rating displaying all 3 stats', () => {
   expect(nCount).toBeTruthy()
   const cCount = screen.getByText('18')
   expect(cCount).toBeTruthy()
+  const vCount = screen.queryByText('7')
+  expect(vCount).toBeNull()
 })
 
-test('support phase displaying 2 stats', () => {
+test('3 phase: support phase - can view comments and support count', () => {
   const permissions = {
     view_support_count: true,
     view_rate_count: false,
-    view_comment_count: true
+    view_comment_count: true,
+    view_vote_count: false
   }
 
   render(
@@ -38,6 +43,7 @@ test('support phase displaying 2 stats', () => {
       positiveCount="4"
       negativeCount="1"
       commentCount="18"
+      voteCount="7"
     />
   )
   const pCount = screen.getByText('4')
@@ -46,4 +52,33 @@ test('support phase displaying 2 stats', () => {
   expect(nCount).toBeNull()
   const cCount = screen.getByText('18')
   expect(cCount).toBeTruthy()
+  const vCount = screen.queryByText('7')
+  expect(vCount).toBeNull()
+})
+
+test('3 phase: finished - can view comments and vote count', () => {
+  const permissions = {
+    view_support_count: false,
+    view_rate_count: false,
+    view_comment_count: true,
+    view_vote_count: true
+  }
+
+  render(
+    <ListItemStats
+      permissions={permissions}
+      positiveCount="4"
+      negativeCount="1"
+      commentCount="18"
+      voteCount="7"
+    />
+  )
+  const pCount = screen.queryByText('4')
+  expect(pCount).toBeNull()
+  const nCount = screen.queryByText('1')
+  expect(nCount).toBeNull()
+  const cCount = screen.getByText('18')
+  expect(cCount).toBeTruthy()
+  const vCount = screen.getByText('7')
+  expect(vCount).toBeTruthy()
 })