fix: fix profile clicks from compose dialog (#1160)

* fix: don't allow profile clicks from compose dialog

fixes #1159

* make the links work correctly in the modal

* fix tests
This commit is contained in:
Nolan Lawson 2019-04-20 09:12:30 -07:00 committed by GitHub
parent 2ce2453d8f
commit 1712081f0b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 59 additions and 32 deletions

View file

@ -1,16 +1,20 @@
<a {href}
rel="prefetch"
class="compose-box-avatar {loaded ? 'loaded' : 'not-loaded'}"
aria-hidden={!loaded}
aria-label="Profile for {accessibleName}">
aria-hidden={true}
on:click="onClick(event)"
>
<Avatar account={verifyCredentials} size="small"/>
</a>
<a class="compose-box-display-name {loaded ? 'loaded' : 'not-loaded'}"
{href}
aria-hidden={!loaded}
rel="prefetch">
rel="prefetch"
on:click="onClick(event)"
>
<AccountDisplayName account={verifyCredentials} />
</a>
<span class="compose-box-handle {loaded ? 'loaded' : 'not-loaded'}"
aria-hidden={!loaded} >
{'@' + verifyCredentials.acct}
@ -64,8 +68,9 @@
import Avatar from '../Avatar.html'
import { store } from '../../_store/store'
import AccountDisplayName from '../profile/AccountDisplayName.html'
import { removeEmoji } from '../../_utils/removeEmoji'
import { ONE_TRANSPARENT_PIXEL } from '../../_static/media'
import { emit } from '../../_utils/eventBus'
import { goto } from '../../../../__sapper__/client'
export default {
components: {
@ -86,14 +91,20 @@
}
},
id: ({ verifyCredentials }) => (verifyCredentials && verifyCredentials.id),
href: ({ id }) => (id ? `/accounts/${id}` : '#'),
emojis: ({ verifyCredentials }) => (verifyCredentials.emojis || []),
displayName: ({ verifyCredentials }) => verifyCredentials.display_name || verifyCredentials.username || '',
accessibleName: ({ displayName, emojis, $omitEmojiInDisplayNames }) => {
if ($omitEmojiInDisplayNames) {
return removeEmoji(displayName, emojis) || displayName
href: ({ id }) => (id ? `/accounts/${id}` : '#')
},
methods: {
onClick (e) {
let { realm, dialogId, href } = this.get()
if (realm === 'dialog') {
e.preventDefault()
e.stopPropagation()
// in dialog mode, we have to close the dialog and then navigate to the profile
emit('closeDialog', dialogId)
setTimeout(() => { // setTimeout to work around dialog navigation issues
goto(href)
})
}
return displayName
}
}
}

View file

@ -3,7 +3,7 @@
{/if}
<ComposeFileDrop {realm} >
<div class="{computedClassName} {hideAndFadeIn}">
<ComposeAuthor />
<ComposeAuthor {realm} {dialogId} />
{#if contentWarningShown}
<div class="compose-content-warning-wrapper"
transition:slide="{duration: 333}">
@ -120,7 +120,8 @@
isReply: false,
autoFocus: false,
hideBottomBorder: false,
hidden: false
hidden: false,
dialogId: void 0
}),
store: () => store,
computed: {

View file

@ -4,7 +4,7 @@
{title}
background="var(--main-bg)"
>
<ComposeBox realm="dialog" autoFocus="true" />
<ComposeBox realm="dialog" autoFocus={true} dialogId={id} />
</ModalDialog>
<script>
import ModalDialog from './ModalDialog.html'

View file

@ -6,7 +6,7 @@ import {
dialogOptionsOption,
getNthStatus,
getNthStatusMediaButton,
getNthStatusOptionsButton,
getNthStatusOptionsButton, getScrollTop,
getUrl,
goBack,
goForward,
@ -20,7 +20,7 @@ import {
visibleModalDialog
} from '../utils'
import { loginAsFoobar } from '../roles'
import { Selector as $ } from 'testcafe'
import { homeTimeline } from '../fixtures'
fixture`029-back-button-modal.js`
@ -259,3 +259,20 @@ test('History works correctly for nested modal 3', async t => {
await t
.expect(getUrl()).contains('/notifications')
})
test('History and scroll position work correctly for link in compose dialog', async t => {
await loginAsFoobar(t)
await scrollToStatus(t, 10)
await t
.expect(getScrollTop()).notEql(0)
.click(composeButton)
.expect(modalDialog.hasAttribute('aria-hidden')).notOk()
.expect(composeModalInput.exists).ok()
.click($('.modal-dialog-document .compose-box-display-name'))
.expect(getUrl()).contains('/accounts')
.expect(getScrollTop()).eql(0)
await goBack()
await t
.expect(getUrl()).eql('http://localhost:4002/')
.expect(getScrollTop()).notEql(0)
})

View file

@ -1,6 +1,5 @@
import { loginAsFoobar } from '../roles'
import {
avatarInComposeBox,
displayNameInComposeBox,
generalSettingsButton,
getNthStatus,
@ -14,6 +13,8 @@ import {
import { postAs, updateUserDisplayNameAs } from '../serverActions'
import { Selector as $ } from 'testcafe'
const rainbow = String.fromCodePoint(0x1F308)
fixture`118-display-name-custom-emoji.js`
.page`http://localhost:4002`
@ -38,59 +39,52 @@ test('Cannot XSS using display name HTML', async t => {
})
test('Can remove emoji from user display names', async t => {
await updateUserDisplayNameAs('foobar', '🌈 foo :blobpats: 🌈')
await updateUserDisplayNameAs('foobar', `${rainbow} foo :blobpats: ${rainbow}`)
await sleep(1000)
await loginAsFoobar(t)
await t
.expect(displayNameInComposeBox.innerText).match(/🌈\s+foo\s+🌈/)
.expect($('.compose-box-display-name img').exists).ok()
.expect(avatarInComposeBox.getAttribute('aria-label')).eql('Profile for 🌈 foo :blobpats: 🌈')
.expect(displayNameInComposeBox.innerText).eql(`${rainbow} foo ${rainbow}`)
.click(settingsNavButton)
.click(generalSettingsButton)
.click(removeEmojiFromDisplayNamesInput)
.expect(removeEmojiFromDisplayNamesInput.checked).ok()
.click(homeNavButton)
.expect(displayNameInComposeBox.innerText).eql('foo')
.expect($('.compose-box-display-name img').exists).notOk()
.expect(avatarInComposeBox.getAttribute('aria-label')).eql('Profile for foo')
.expect(displayNameInComposeBox.innerText).eql('foo')
.click(settingsNavButton)
.click(generalSettingsButton)
.click(removeEmojiFromDisplayNamesInput)
.expect(removeEmojiFromDisplayNamesInput.checked).notOk()
.click(homeNavButton)
.expect(displayNameInComposeBox.innerText).match(/🌈\s+foo\s+🌈/)
.expect($('.compose-box-display-name img').exists).ok()
.expect(avatarInComposeBox.getAttribute('aria-label')).eql('Profile for 🌈 foo :blobpats: 🌈')
.expect(displayNameInComposeBox.innerText).eql(`${rainbow} foo ${rainbow}`)
})
test('Cannot remove emoji from user display names if result would be empty', async t => {
await updateUserDisplayNameAs('foobar', '🌈 :blobpats: 🌈')
await updateUserDisplayNameAs('foobar', `${rainbow} :blobpats: ${rainbow}`)
await sleep(1000)
await loginAsFoobar(t)
await t
.expect(displayNameInComposeBox.innerText).match(/🌈\s+🌈/)
.expect(displayNameInComposeBox.innerText).eql(`${rainbow} ${rainbow}`)
.expect($('.compose-box-display-name img').exists).ok()
.expect(avatarInComposeBox.getAttribute('aria-label')).eql('Profile for 🌈 :blobpats: 🌈')
.click(settingsNavButton)
.click(generalSettingsButton)
.click(removeEmojiFromDisplayNamesInput)
.expect(removeEmojiFromDisplayNamesInput.checked).ok()
.click(homeNavButton)
.expect(displayNameInComposeBox.innerText).match(/🌈\s+🌈/)
.expect(displayNameInComposeBox.innerText).eql(`${rainbow} ${rainbow}`)
.expect($('.compose-box-display-name img').exists).ok()
.expect(avatarInComposeBox.getAttribute('aria-label')).eql('Profile for 🌈 :blobpats: 🌈')
.click(settingsNavButton)
.click(generalSettingsButton)
.click(removeEmojiFromDisplayNamesInput)
.expect(removeEmojiFromDisplayNamesInput.checked).notOk()
.click(homeNavButton)
.expect(displayNameInComposeBox.innerText).match(/🌈\s+🌈/)
.expect(displayNameInComposeBox.innerText).eql(`${rainbow} ${rainbow}`)
.expect($('.compose-box-display-name img').exists).ok()
.expect(avatarInComposeBox.getAttribute('aria-label')).eql('Profile for 🌈 :blobpats: 🌈')
})
test('Check status aria labels for de-emojified text', async t => {
let rainbow = String.fromCodePoint(0x1F308)
await updateUserDisplayNameAs('foobar', `${rainbow} foo :blobpats: ${rainbow}`)
await postAs('foobar', 'hey ho lotsa emojos')
await sleep(1000)

View file

@ -189,6 +189,10 @@ export const scrollToTop = exec(() => {
document.scrollingElement.scrollTop = 0
})
export const getScrollTop = exec(() => {
return document.scrollingElement.scrollTop || 0
})
export function getNthMediaAltInput (n) {
return $(`.compose-box .compose-media:nth-child(${n}) .compose-media-alt input`)
}