Skip to content

Commit

Permalink
adhocracy4/follows: on click redirect to login page if user is not lo…
Browse files Browse the repository at this point in the history
…gged in
  • Loading branch information
goapunk authored and philli-m committed Oct 25, 2023
1 parent 76cb784 commit aca7fd9
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 33 deletions.
67 changes: 40 additions & 27 deletions adhocracy4/follows/static/follows/FollowButton.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@ import django from 'django'
import React, { useState, useEffect } from 'react'
import Alert from '../../../static/Alert'

const api = require('../../../static/api')
import api from '../../../static/api'
import config from '../../../static/config'

const translated = {
followDescription: django.gettext('Click to be updated about this project via email.'),
followingDescription: django.gettext('Click to no longer be updated about this project via email.'),
followDescription: django.gettext(
'Click to be updated about this project via email.'
),
followingDescription: django.gettext(
'Click to no longer be updated about this project via email.'
),
followAlert: django.gettext('You will be updated via email.'),
followingAlert: django.gettext('You will no longer be updated via email.'),
follow: django.gettext('Follow'),
Expand All @@ -17,9 +22,7 @@ export const FollowButton = (props) => {
const [following, setFollowing] = useState(null)
const [alert, setAlert] = useState(null)

const followBtnText = following
? translated.following
: translated.follow
const followBtnText = following ? translated.following : translated.follow

const followDescriptionText = following
? translated.followingDescription
Expand All @@ -30,44 +33,54 @@ export const FollowButton = (props) => {
: translated.followAlert

useEffect(() => {
api.follow.get(props.project)
.done((follow) => {
setFollowing(follow.enabled)
setAlert(follow.alert)
})
.fail((response) => {
if (response.status === 404) {
setFollowing(false)
}
})
}, [props.project])
if (props.authenticatedAs) {
api.follow
.get(props.project)
.done((follow) => {
setFollowing(follow.enabled)
setAlert(follow.alert)
})
.fail((response) => {
if (response.status === 404) {
setFollowing(false)
}
})
}
}, [props.project, props.authenticatedAs])

const removeAlert = () => {
setAlert(null)
}

const toggleFollow = () => {
api.follow.change({ enabled: !following }, props.project)
.done((follow) => {
setFollowing(follow.enabled)
setAlert({
type: 'success',
message: followAlertText
})
if (props.authenticatedAs === null) {
window.location.href = config.getLoginUrl()
return
}
api.follow.change({ enabled: !following }, props.project).done((follow) => {
setFollowing(follow.enabled)
setAlert({
type: 'success',
message: followAlertText
})
})
}

return (
<span className="a4-follow">
<button
className={following ? 'a4-btn a4-btn--following' : 'a4-btn a4-btn--follow'}
className={
following ? 'a4-btn a4-btn--following' : 'a4-btn a4-btn--follow'
}
type="button"
onClick={toggleFollow}
aria-describedby="follow-description"
disabled={following === null}
disabled={following === null && props.authenticatedAs !== null}
>
<span className="a4-follow__btn--content">{followBtnText}</span>
<span className="a4-sr-only" id="follow-description">{followDescriptionText}</span>
<span className="a4-sr-only" id="follow-description">
{followDescriptionText}
</span>
</button>
<span className="a4-follow__notification">
<Alert onClick={removeAlert} {...alert} />
Expand Down
68 changes: 68 additions & 0 deletions adhocracy4/follows/static/follows/__tests__/FollowButton.jest.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React from 'react'
import { render, fireEvent, screen } from '@testing-library/react'
import '@testing-library/jest-dom'
import { FollowButton } from '../FollowButton'
import api from '../../../../static/api'

// mock api and config, as they rely on network and browser
jest.mock('../../../../static/config')
jest.mock('../../../../static/api')

afterEach(() => {
jest.clearAllMocks()
})

test('Test render FollowButton not following', async () => {
api.follow.setFollowing({ enabled: false })
render(<FollowButton authenticatedAs project="test" />)
const followButton = await screen.findByText('Follow')
expect(followButton).toBeTruthy()
const followingButton = screen.queryByText('Following')
expect(followingButton).toBeNull()
expect(api.follow.get).toHaveBeenCalledTimes(1)
})

test('Test render FollowButton following', async () => {
api.follow.setFollowing({ enabled: true })
render(<FollowButton authenticatedAs project="test" />)
const followingButton = await screen.findByText('Following')
expect(followingButton).toBeTruthy()
const followButton = screen.queryByText('Follow')
expect(followButton).toBeNull()
expect(api.follow.get).toHaveBeenCalledTimes(1)
})

test('Test render FollowButton click follow', async () => {
api.follow.setFollowing({ enabled: false })
render(<FollowButton authenticatedAs project="test" />)
let followButton = await screen.findByText('Follow')
expect(followButton).toBeTruthy()
let followingButton = screen.queryByText('Following')
expect(followingButton).toBeNull()
fireEvent.click(followButton)
followingButton = await screen.findByText('Following')
expect(followingButton).toBeTruthy()
followButton = screen.queryByText('Follow')
expect(followButton).toBeNull()
expect(api.follow.change).toHaveBeenCalledTimes(1)
expect(api.follow.get).toHaveBeenCalledTimes(1)
})

test('Test FollowButton redirect', async () => {
// testing the redirect doesn't work and will throw an exception
// as we are not in a browser.
// workaround: delete location and simply check if href is set
// to "correct" url
delete window.location
window.location = {}
api.follow.setFollowing({ enabled: false })
render(<FollowButton authenticatedAs={null} project="test" />)
const followButton = await screen.findByText('Follow')
expect(followButton).toBeTruthy()
const followingButton = screen.queryByText('Following')
expect(followingButton).toBeNull()
fireEvent.click(followButton)
expect(window.location.href).toBe('/mock-url')
expect(api.follow.change).not.toHaveBeenCalled()
expect(api.follow.get).not.toHaveBeenCalled()
})
4 changes: 2 additions & 2 deletions adhocracy4/follows/static/follows/react_follows.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { createRoot } from 'react-dom/client'
import { FollowButton } from './FollowButton'

module.exports.renderFollow = function (el) {
const project = el.getAttribute('data-project')
const props = JSON.parse(el.getAttribute('data-attributes'))
const root = createRoot(el)
root.render(<FollowButton project={project} />)
root.render(<FollowButton {...props} />)
}
16 changes: 12 additions & 4 deletions adhocracy4/follows/templatetags/react_follows.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
import json

from django import template
from django.utils.html import format_html

register = template.Library()


@register.simple_tag()
def react_follows(project):
@register.simple_tag(takes_context=True)
def react_follows(context, project):
request = context["request"]
user = request.user
authenticated_as = None
if user.is_authenticated:
authenticated_as = user.username
attributes = {"project": project.name, "authenticatedAs": authenticated_as}
return format_html(
'<span data-a4-widget="follows" data-project={project}></span>',
project=project.slug,
'<span data-a4-widget="follows" data-attributes="{attributes}"></span>',
attributes=json.dumps(attributes),
)
46 changes: 46 additions & 0 deletions adhocracy4/static/__mocks__/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
let following = null

const api = {
follow: {
get: jest.fn(() => {
const instance = {
done: (fn) => {
if (following !== null) {
fn(following)
}
return instance
},
fail: (fn) => {
if (following === null) {
fn({ status: 400 })
}
return instance
}
}
return instance
}),
change: jest.fn((enabled) => {
following = { enabled }
const instance = {
done: (fn) => {
if (following !== null) {
fn(following)
}
return instance
},
fail: (fn) => {
if (following === null) {
fn({ status: 400 })
}
return instance
}
}
return instance
}),
setFollowing: (value) => {
following = value
}
}
}

module.exports = api
3 changes: 3 additions & 0 deletions adhocracy4/static/__mocks__/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
getLoginUrl: () => '/mock-url'
}
2 changes: 2 additions & 0 deletions changelog/7618.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
### Changed

- refactor follow to be functional and add aria described by for when no alert shown and use a4 prefix for classes so external style liberies can be used (story !7618/7701)
- redirect to login page when follow button is pressed and user is not logged
in

0 comments on commit aca7fd9

Please sign in to comment.