feat: implement notification filters (all vs mentions) (#1177)
fixes #1176
This commit is contained in:
parent
ff1e9e2c41
commit
23bdc6c87e
|
@ -6,15 +6,22 @@ import { addStatusOrNotification } from './addStatusOrNotification'
|
||||||
function processMessage (instanceName, timelineName, message) {
|
function processMessage (instanceName, timelineName, message) {
|
||||||
mark('processMessage')
|
mark('processMessage')
|
||||||
let { event, payload } = message
|
let { event, payload } = message
|
||||||
|
if (['update', 'notification', 'conversation'].includes(event)) {
|
||||||
|
payload = JSON.parse(payload) // only these payloads are JSON-encoded for some reason
|
||||||
|
}
|
||||||
|
|
||||||
switch (event) {
|
switch (event) {
|
||||||
case 'delete':
|
case 'delete':
|
||||||
deleteStatus(instanceName, payload)
|
deleteStatus(instanceName, payload)
|
||||||
break
|
break
|
||||||
case 'update':
|
case 'update':
|
||||||
addStatusOrNotification(instanceName, timelineName, JSON.parse(payload))
|
addStatusOrNotification(instanceName, timelineName, payload)
|
||||||
break
|
break
|
||||||
case 'notification':
|
case 'notification':
|
||||||
addStatusOrNotification(instanceName, 'notifications', JSON.parse(payload))
|
addStatusOrNotification(instanceName, 'notifications', payload)
|
||||||
|
if (payload.type === 'mention') {
|
||||||
|
addStatusOrNotification(instanceName, 'notifications/mentions', payload)
|
||||||
|
}
|
||||||
break
|
break
|
||||||
case 'conversation':
|
case 'conversation':
|
||||||
// This is a hack in order to mostly fit the conversation model into
|
// This is a hack in order to mostly fit the conversation model into
|
||||||
|
@ -22,7 +29,7 @@ function processMessage (instanceName, timelineName, message) {
|
||||||
// reproduce what is done for statuses for the conversation.
|
// reproduce what is done for statuses for the conversation.
|
||||||
//
|
//
|
||||||
// It will add new DMs as new conversations instead of updating existing threads
|
// It will add new DMs as new conversations instead of updating existing threads
|
||||||
addStatusOrNotification(instanceName, timelineName, JSON.parse(payload).last_status)
|
addStatusOrNotification(instanceName, timelineName, payload.last_status)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
stop('processMessage')
|
stop('processMessage')
|
||||||
|
|
|
@ -9,6 +9,7 @@ function getTimelineUrlPath (timeline) {
|
||||||
case 'home':
|
case 'home':
|
||||||
return 'timelines/home'
|
return 'timelines/home'
|
||||||
case 'notifications':
|
case 'notifications':
|
||||||
|
case 'notifications/mentions':
|
||||||
return 'notifications'
|
return 'notifications'
|
||||||
case 'favorites':
|
case 'favorites':
|
||||||
return 'favourites'
|
return 'favourites'
|
||||||
|
@ -61,6 +62,10 @@ export async function getTimeline (instanceName, accessToken, timeline, maxId, s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (timeline === 'notifications/mentions') {
|
||||||
|
params.exclude_types = ['follow', 'favourite', 'reblog', 'poll']
|
||||||
|
}
|
||||||
|
|
||||||
url += '?' + paramsString(params)
|
url += '?' + paramsString(params)
|
||||||
|
|
||||||
const items = await get(url, auth(accessToken), { timeout: DEFAULT_TIMEOUT })
|
const items = await get(url, auth(accessToken), { timeout: DEFAULT_TIMEOUT })
|
||||||
|
|
|
@ -146,7 +146,11 @@
|
||||||
},
|
},
|
||||||
store: () => store,
|
store: () => store,
|
||||||
computed: {
|
computed: {
|
||||||
selected: ({ page, name }) => page === name,
|
selected: ({ page, name }) => {
|
||||||
|
return page === name ||
|
||||||
|
// special case – these should both highlight the notifications tab icon
|
||||||
|
(name === 'notifications' && page === 'notifications/mentions')
|
||||||
|
},
|
||||||
ariaLabel: ({ selected, name, label, $numberOfNotifications }) => {
|
ariaLabel: ({ selected, name, label, $numberOfNotifications }) => {
|
||||||
let res = label
|
let res = label
|
||||||
if (selected) {
|
if (selected) {
|
||||||
|
|
29
src/routes/_components/NotificationFilters.html
Normal file
29
src/routes/_components/NotificationFilters.html
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
<TabSet
|
||||||
|
label="Filters"
|
||||||
|
currentTabName={filter}
|
||||||
|
{tabs}
|
||||||
|
className="notification-filters"
|
||||||
|
/>
|
||||||
|
<script>
|
||||||
|
import TabSet from './TabSet.html'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data: () => ({
|
||||||
|
tabs: [
|
||||||
|
{
|
||||||
|
name: '',
|
||||||
|
label: 'All',
|
||||||
|
href: `/notifications`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'mentions',
|
||||||
|
label: 'Mentions',
|
||||||
|
href: `/notifications/mentions`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
components: {
|
||||||
|
TabSet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
89
src/routes/_components/TabSet.html
Normal file
89
src/routes/_components/TabSet.html
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
<nav aria-label={label} class={className}>
|
||||||
|
<ul>
|
||||||
|
{#each tabs as tab (tab.name)}
|
||||||
|
<li class="{currentTabName === tab.name ? 'current' : 'not-current'}">
|
||||||
|
<a aria-label="{tab.label} { currentTabName === tab.name ? '(Current)' : ''}"
|
||||||
|
href={tab.href}
|
||||||
|
rel="prefetch">
|
||||||
|
{tab.label}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<style>
|
||||||
|
li {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* reset */
|
||||||
|
ul, li {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
display: flex;
|
||||||
|
margin: 5px 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
border: 1px solid var(--main-border);
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
border-top-left-radius: 10px;
|
||||||
|
border-top-right-radius: 10px;
|
||||||
|
background: var(--tab-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
li:not(:first-child) {
|
||||||
|
border-left: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
li:hover {
|
||||||
|
background: var(--button-bg-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
li.not-current {
|
||||||
|
background: var(--tab-bg-non-selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
li.current {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
li.current:hover {
|
||||||
|
background: var(--tab-bg-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
li.not-current:hover {
|
||||||
|
background: var(--tab-bg-hover-non-selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
li:active {
|
||||||
|
background: var(--tab-bg-active);
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
padding: 10px;
|
||||||
|
color: var(--body-text-color);
|
||||||
|
font-size: 1.1em;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data: () => ({
|
||||||
|
className: ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -1,107 +1,36 @@
|
||||||
<nav aria-label="Filters" class="account-profile-filters">
|
<TabSet
|
||||||
<ul>
|
label="Filters"
|
||||||
{#each filterTabs as filterTab (filterTab.href)}
|
currentTabName={filter}
|
||||||
<li class="{filter === filterTab.filter ? 'current-filter' : 'not-current-filter'}">
|
{tabs}
|
||||||
<a aria-label="{filterTab.label} { filter === filterTab.filter ? '(Current)' : ''}"
|
className="account-profile-filters"
|
||||||
href={filterTab.href}
|
/>
|
||||||
rel="prefetch">
|
|
||||||
{filterTab.label}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
<style>
|
|
||||||
li {
|
|
||||||
flex: 1;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* reset */
|
|
||||||
ul, li {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
list-style: none;
|
|
||||||
display: flex;
|
|
||||||
margin: 5px 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
border: 1px solid var(--main-border);
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
border-top-left-radius: 10px;
|
|
||||||
border-top-right-radius: 10px;
|
|
||||||
background: var(--tab-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
li:not(:first-child) {
|
|
||||||
border-left: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
li:hover {
|
|
||||||
background: var(--button-bg-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
li.not-current-filter {
|
|
||||||
background: var(--tab-bg-non-selected);
|
|
||||||
}
|
|
||||||
|
|
||||||
li.current-filter {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
li.current-filter:hover {
|
|
||||||
background: var(--tab-bg-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
li.not-current-filter:hover {
|
|
||||||
background: var(--tab-bg-hover-non-selected);
|
|
||||||
}
|
|
||||||
|
|
||||||
li:active {
|
|
||||||
background: var(--tab-bg-active);
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
padding: 10px;
|
|
||||||
color: var(--body-text-color);
|
|
||||||
font-size: 1.1em;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<script>
|
<script>
|
||||||
|
import TabSet from '../TabSet.html'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
computed: {
|
computed: {
|
||||||
filterTabs: ({ account }) => (
|
tabs: ({ account }) => (
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
filter: '',
|
name: '',
|
||||||
label: 'Toots',
|
label: 'Toots',
|
||||||
href: `/accounts/${account.id}`
|
href: `/accounts/${account.id}`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
filter: 'with_replies',
|
name: 'with_replies',
|
||||||
label: 'Toots and replies',
|
label: 'Toots and replies',
|
||||||
href: `/accounts/${account.id}/with_replies`
|
href: `/accounts/${account.id}/with_replies`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
filter: 'media',
|
name: 'media',
|
||||||
label: 'Media',
|
label: 'Media',
|
||||||
href: `/accounts/${account.id}/media`
|
href: `/accounts/${account.id}/media`
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
TabSet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -111,7 +111,7 @@ async function insertStatusThread (instanceName, statusId, statuses) {
|
||||||
|
|
||||||
export async function insertTimelineItems (instanceName, timeline, timelineItems) {
|
export async function insertTimelineItems (instanceName, timeline, timelineItems) {
|
||||||
/* no await */ scheduleCleanup()
|
/* no await */ scheduleCleanup()
|
||||||
if (timeline === 'notifications') {
|
if (timeline === 'notifications' || timeline === 'notifications/mentions') {
|
||||||
return insertTimelineNotifications(instanceName, timeline, timelineItems)
|
return insertTimelineNotifications(instanceName, timeline, timelineItems)
|
||||||
} else if (timeline.startsWith('status/')) {
|
} else if (timeline.startsWith('status/')) {
|
||||||
let statusId = timeline.split('/').slice(-1)[0]
|
let statusId = timeline.split('/').slice(-1)[0]
|
||||||
|
|
|
@ -84,7 +84,7 @@ async function getStatusThread (instanceName, statusId) {
|
||||||
export async function getTimeline (instanceName, timeline, maxId, limit) {
|
export async function getTimeline (instanceName, timeline, maxId, limit) {
|
||||||
maxId = maxId || null
|
maxId = maxId || null
|
||||||
limit = limit || TIMELINE_BATCH_SIZE
|
limit = limit || TIMELINE_BATCH_SIZE
|
||||||
if (timeline === 'notifications') {
|
if (timeline === 'notifications' || timeline === 'notifications/mentions') {
|
||||||
return getNotificationTimeline(instanceName, timeline, maxId, limit)
|
return getNotificationTimeline(instanceName, timeline, maxId, limit)
|
||||||
} else if (timeline.startsWith('status/')) {
|
} else if (timeline.startsWith('status/')) {
|
||||||
let statusId = timeline.split('/').slice(-1)[0]
|
let statusId = timeline.split('/').slice(-1)[0]
|
||||||
|
|
|
@ -90,6 +90,7 @@
|
||||||
<a href="/federated">Federated</a>
|
<a href="/federated">Federated</a>
|
||||||
<a href="/favorites">Favorites</a>
|
<a href="/favorites">Favorites</a>
|
||||||
<a href="/direct">Conversations</a>
|
<a href="/direct">Conversations</a>
|
||||||
|
<a href="/notifications/mentions">Notification mentions</a>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
{#if $isUserLoggedIn}
|
|
||||||
<TimelinePage timeline="notifications" />
|
|
||||||
{:else}
|
|
||||||
<HiddenFromSSR>
|
|
||||||
<FreeTextLayout>
|
|
||||||
<h1>Notifications</h1>
|
|
||||||
|
|
||||||
<p>Your notifications will appear here when logged in.</p>
|
|
||||||
</FreeTextLayout>
|
|
||||||
</HiddenFromSSR>
|
|
||||||
{/if}
|
|
||||||
<script>
|
|
||||||
import FreeTextLayout from '../_components/FreeTextLayout.html'
|
|
||||||
import { store } from '../_store/store.js'
|
|
||||||
import HiddenFromSSR from '../_components/HiddenFromSSR'
|
|
||||||
import TimelinePage from '../_components/TimelinePage.html'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
store: () => store,
|
|
||||||
components: {
|
|
||||||
FreeTextLayout,
|
|
||||||
HiddenFromSSR,
|
|
||||||
TimelinePage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
29
src/routes/_pages/notifications/index.html
Normal file
29
src/routes/_pages/notifications/index.html
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
{#if $isUserLoggedIn}
|
||||||
|
<NotificationFilters filter="" />
|
||||||
|
<TimelinePage timeline="notifications" />
|
||||||
|
{:else}
|
||||||
|
<HiddenFromSSR>
|
||||||
|
<FreeTextLayout>
|
||||||
|
<h1>Notifications</h1>
|
||||||
|
|
||||||
|
<p>Your notifications will appear here when logged in.</p>
|
||||||
|
</FreeTextLayout>
|
||||||
|
</HiddenFromSSR>
|
||||||
|
{/if}
|
||||||
|
<script>
|
||||||
|
import FreeTextLayout from '../../_components/FreeTextLayout.html'
|
||||||
|
import { store } from '../../_store/store.js'
|
||||||
|
import HiddenFromSSR from '../../_components/HiddenFromSSR'
|
||||||
|
import TimelinePage from '../../_components/TimelinePage.html'
|
||||||
|
import NotificationFilters from '../../_components/NotificationFilters.html'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
store: () => store,
|
||||||
|
components: {
|
||||||
|
FreeTextLayout,
|
||||||
|
HiddenFromSSR,
|
||||||
|
TimelinePage,
|
||||||
|
NotificationFilters
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
29
src/routes/_pages/notifications/mentions.html
Normal file
29
src/routes/_pages/notifications/mentions.html
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
{#if $isUserLoggedIn}
|
||||||
|
<NotificationFilters filter="mentions" />
|
||||||
|
<TimelinePage timeline="notifications/mentions" />
|
||||||
|
{:else}
|
||||||
|
<HiddenFromSSR>
|
||||||
|
<FreeTextLayout>
|
||||||
|
<h1>Notification mentions</h1>
|
||||||
|
|
||||||
|
<p>Your notification mentions will appear here when logged in.</p>
|
||||||
|
</FreeTextLayout>
|
||||||
|
</HiddenFromSSR>
|
||||||
|
{/if}
|
||||||
|
<script>
|
||||||
|
import FreeTextLayout from '../../_components/FreeTextLayout.html'
|
||||||
|
import { store } from '../../_store/store.js'
|
||||||
|
import HiddenFromSSR from '../../_components/HiddenFromSSR'
|
||||||
|
import TimelinePage from '../../_components/TimelinePage.html'
|
||||||
|
import NotificationFilters from '../../_components/NotificationFilters.html'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
store: () => store,
|
||||||
|
components: {
|
||||||
|
FreeTextLayout,
|
||||||
|
HiddenFromSSR,
|
||||||
|
TimelinePage,
|
||||||
|
NotificationFilters
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -74,11 +74,16 @@ export async function del (url, headers, options) {
|
||||||
|
|
||||||
export function paramsString (paramsObject) {
|
export function paramsString (paramsObject) {
|
||||||
let res = ''
|
let res = ''
|
||||||
Object.keys(paramsObject).forEach((key, i) => {
|
let count = -1
|
||||||
if (i > 0) {
|
Object.keys(paramsObject).forEach(key => {
|
||||||
res += '&'
|
let value = paramsObject[key]
|
||||||
|
if (Array.isArray(value)) { // rails convention for encoding multiple values
|
||||||
|
for (let item of value) {
|
||||||
|
res += (++count > 0 ? '&' : '') + encodeURIComponent(key) + '[]=' + encodeURIComponent(item)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res += (++count > 0 ? '&' : '') + encodeURIComponent(key) + '=' + encodeURIComponent(value)
|
||||||
}
|
}
|
||||||
res += encodeURIComponent(key) + '=' + encodeURIComponent(paramsObject[key])
|
|
||||||
})
|
})
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,9 @@
|
||||||
<LazyPage {pageComponent} {params} />
|
<LazyPage {pageComponent} {params} />
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Title from './_components/Title.html'
|
import Title from '../_components/Title.html'
|
||||||
import LazyPage from './_components/LazyPage.html'
|
import LazyPage from '../_components/LazyPage.html'
|
||||||
import pageComponent from './_pages/notifications.html'
|
import pageComponent from '../_pages/notifications/index.html'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
20
src/routes/notifications/mentions.html
Normal file
20
src/routes/notifications/mentions.html
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<Title name="Notifications" />
|
||||||
|
|
||||||
|
<LazyPage {pageComponent} {params} />
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Title from '../_components/Title.html'
|
||||||
|
import LazyPage from '../_components/LazyPage.html'
|
||||||
|
import pageComponent from '../_pages/notifications/mentions.html'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
|
||||||
|
Title,
|
||||||
|
LazyPage
|
||||||
|
},
|
||||||
|
data: () => ({
|
||||||
|
pageComponent
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -44,6 +44,13 @@ export const notifications = [
|
||||||
{ followedBy: 'admin' }
|
{ followedBy: 'admin' }
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export const notificationsMentions = [
|
||||||
|
{ content: 'notification of unlisted message' },
|
||||||
|
{ content: 'notification of followers-only message' },
|
||||||
|
{ content: 'notification of direct message' },
|
||||||
|
{ content: 'hello foobar' }
|
||||||
|
]
|
||||||
|
|
||||||
export const favorites = [
|
export const favorites = [
|
||||||
{ content: 'notification of direct message' },
|
{ content: 'notification of direct message' },
|
||||||
{ content: 'notification of followers-only message' },
|
{ content: 'notification of followers-only message' },
|
||||||
|
|
22
tests/spec/033-notification-filters.js
Normal file
22
tests/spec/033-notification-filters.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import {
|
||||||
|
getUrl, notificationFiltersAll, notificationFiltersMention,
|
||||||
|
notificationsNavButton, validateTimeline
|
||||||
|
} from '../utils'
|
||||||
|
import { loginAsFoobar } from '../roles'
|
||||||
|
import { notificationsMentions, notifications } from '../fixtures'
|
||||||
|
|
||||||
|
fixture`033-notification-filters.js`
|
||||||
|
.page`http://localhost:4002`
|
||||||
|
|
||||||
|
test('Shows notification filters', async t => {
|
||||||
|
await loginAsFoobar(t)
|
||||||
|
await t
|
||||||
|
.click(notificationsNavButton)
|
||||||
|
.expect(getUrl()).match(/\/notifications$/)
|
||||||
|
.click(notificationFiltersMention)
|
||||||
|
.expect(getUrl()).match(/\/notifications\/mentions$/)
|
||||||
|
await validateTimeline(t, notificationsMentions)
|
||||||
|
await t.click(notificationFiltersAll)
|
||||||
|
.expect(getUrl()).match(/\/notifications$/)
|
||||||
|
await validateTimeline(t, notifications)
|
||||||
|
})
|
65
tests/spec/123-notification-filters.js
Normal file
65
tests/spec/123-notification-filters.js
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import {
|
||||||
|
getNthStatusContent,
|
||||||
|
getUrl, notificationFiltersAll, notificationFiltersMention,
|
||||||
|
notificationsNavButton, sleep
|
||||||
|
} from '../utils'
|
||||||
|
import { loginAsFoobar } from '../roles'
|
||||||
|
import { favoriteStatusAs, postAs } from '../serverActions'
|
||||||
|
|
||||||
|
fixture`123-notification-filters.js`
|
||||||
|
.page`http://localhost:4002`
|
||||||
|
|
||||||
|
// maybe in the "mentions" view it should prevent the notification icon from showing (1), (2) etc
|
||||||
|
// if those particular notifications were seen by the user... but this is too hard to implement,
|
||||||
|
// so I'm going to punt on it. Only the "all" view affects those (1) / (2) / etc badges.
|
||||||
|
test('Handles incoming notifications that are mentions', async t => {
|
||||||
|
const timeout = 20000
|
||||||
|
await loginAsFoobar(t)
|
||||||
|
await t
|
||||||
|
.click(notificationsNavButton)
|
||||||
|
.expect(getUrl()).match(/\/notifications$/)
|
||||||
|
.click(notificationFiltersMention)
|
||||||
|
.expect(getUrl()).match(/\/notifications\/mentions$/)
|
||||||
|
await sleep(2000)
|
||||||
|
await postAs('admin', 'hey @foobar I am mentioning you')
|
||||||
|
await t
|
||||||
|
.expect(notificationsNavButton.getAttribute('aria-label')).eql('Notifications (current page) (1 notification)', {
|
||||||
|
timeout
|
||||||
|
})
|
||||||
|
.expect(getNthStatusContent(1).innerText).contains('hey @foobar I am mentioning you')
|
||||||
|
.click(notificationFiltersAll)
|
||||||
|
.expect(notificationsNavButton.getAttribute('aria-label')).eql('Notifications (current page)', { timeout })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Handles incoming notifications that are not mentions', async t => {
|
||||||
|
const timeout = 20000
|
||||||
|
let { id: statusId } = await postAs('foobar', 'this is a post that I hope somebody will favorite')
|
||||||
|
await sleep(2000)
|
||||||
|
await loginAsFoobar(t)
|
||||||
|
await t
|
||||||
|
.click(notificationsNavButton)
|
||||||
|
.expect(getUrl()).match(/\/notifications$/)
|
||||||
|
.click(notificationFiltersMention)
|
||||||
|
.expect(getUrl()).match(/\/notifications\/mentions$/)
|
||||||
|
await sleep(2000)
|
||||||
|
await postAs('admin', 'woot I am mentioning you again @foobar')
|
||||||
|
await t
|
||||||
|
.expect(notificationsNavButton.getAttribute('aria-label')).eql('Notifications (current page) (1 notification)', {
|
||||||
|
timeout
|
||||||
|
})
|
||||||
|
.expect(getNthStatusContent(1).innerText).contains('woot I am mentioning you again @foobar')
|
||||||
|
await sleep(2000)
|
||||||
|
await favoriteStatusAs('admin', statusId)
|
||||||
|
await t
|
||||||
|
.expect(notificationsNavButton.getAttribute('aria-label')).eql('Notifications (current page) (2 notifications)', {
|
||||||
|
timeout
|
||||||
|
})
|
||||||
|
await sleep(2000)
|
||||||
|
await t
|
||||||
|
.expect(getNthStatusContent(1).innerText).contains('woot I am mentioning you again @foobar')
|
||||||
|
.click(notificationFiltersAll)
|
||||||
|
.expect(notificationsNavButton.getAttribute('aria-label')).eql('Notifications (current page)', { timeout })
|
||||||
|
await t
|
||||||
|
.expect(getNthStatusContent(1).innerText).contains('this is a post that I hope somebody will favorite')
|
||||||
|
.expect(getNthStatusContent(2).innerText).contains('woot I am mentioning you again @foobar')
|
||||||
|
})
|
|
@ -64,6 +64,9 @@ export const accountProfileFilterStatuses = $('.account-profile-filters li:nth-c
|
||||||
export const accountProfileFilterStatusesAndReplies = $('.account-profile-filters li:nth-child(2)')
|
export const accountProfileFilterStatusesAndReplies = $('.account-profile-filters li:nth-child(2)')
|
||||||
export const accountProfileFilterMedia = $('.account-profile-filters li:nth-child(3)')
|
export const accountProfileFilterMedia = $('.account-profile-filters li:nth-child(3)')
|
||||||
|
|
||||||
|
export const notificationFiltersAll = $('.notification-filters li:nth-child(1)')
|
||||||
|
export const notificationFiltersMention = $('.notification-filters li:nth-child(2)')
|
||||||
|
|
||||||
export function getComposeModalNthMediaAltInput (n) {
|
export function getComposeModalNthMediaAltInput (n) {
|
||||||
return $(`.modal-dialog .compose-media:nth-child(${n}) .compose-media-alt input`)
|
return $(`.modal-dialog .compose-media:nth-child(${n}) .compose-media-alt input`)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue