fix: fix duplicates in threads (#1672)

fixes #943
This commit is contained in:
Nolan Lawson 2019-12-14 12:04:36 -08:00 committed by GitHub
parent aa662682f3
commit 1d3859a4e2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 113 additions and 3 deletions

View file

@ -10,6 +10,9 @@ import { getStatus, getStatusContext } from '../_api/statuses'
import { emit } from '../_utils/eventBus' import { emit } from '../_utils/eventBus'
import { TIMELINE_BATCH_SIZE } from '../_static/timelines' import { TIMELINE_BATCH_SIZE } from '../_static/timelines'
import { timelineItemToSummary } from '../_utils/timelineItemToSummary' import { timelineItemToSummary } from '../_utils/timelineItemToSummary'
import uniqBy from 'lodash-es/uniqBy'
const byId = _ => _.id
async function storeFreshTimelineItemsInDatabase (instanceName, timelineName, items) { async function storeFreshTimelineItemsInDatabase (instanceName, timelineName, items) {
await database.insertTimelineItems(instanceName, timelineName, items) await database.insertTimelineItems(instanceName, timelineName, items)
@ -70,7 +73,7 @@ export async function addTimelineItemSummaries (instanceName, timelineName, newS
const oldSummaries = store.getForTimeline(instanceName, timelineName, 'timelineItemSummaries') || [] const oldSummaries = store.getForTimeline(instanceName, timelineName, 'timelineItemSummaries') || []
const oldStale = store.getForTimeline(instanceName, timelineName, 'timelineItemSummariesAreStale') const oldStale = store.getForTimeline(instanceName, timelineName, 'timelineItemSummariesAreStale')
const mergedSummaries = mergeArrays(oldSummaries, newSummaries, compareTimelineItemSummaries) const mergedSummaries = uniqBy(mergeArrays(oldSummaries, newSummaries, compareTimelineItemSummaries), byId)
if (!isEqual(oldSummaries, mergedSummaries)) { if (!isEqual(oldSummaries, mergedSummaries)) {
store.setForTimeline(instanceName, timelineName, { timelineItemSummaries: mergedSummaries }) store.setForTimeline(instanceName, timelineName, { timelineItemSummaries: mergedSummaries })

View file

@ -19,6 +19,7 @@
import ListLazyItem from './ListLazyItem.html' import ListLazyItem from './ListLazyItem.html'
import { listStore } from './listStore' import { listStore } from './listStore'
import { getScrollContainer } from '../../_utils/scrollContainer' import { getScrollContainer } from '../../_utils/scrollContainer'
import { observe } from 'svelte-extras'
function getScrollTopOffset () { function getScrollTopOffset () {
const style = getComputedStyle(document.documentElement) const style = getComputedStyle(document.documentElement)
@ -30,11 +31,20 @@
oncreate () { oncreate () {
const { realm } = this.get() const { realm } = this.get()
this.store.setCurrentRealm(realm) this.store.setCurrentRealm(realm)
if (process.env.NODE_ENV !== 'production') {
this.observe('safeItems', safeItems => {
if (new Set(safeItems).size !== safeItems.length) {
console.error('list of items is not unique:', safeItems)
}
})
}
}, },
ondestroy () { ondestroy () {
this.store.setCurrentRealm(null) this.store.setCurrentRealm(null)
}, },
methods: { methods: {
observe,
itemInitialized () { itemInitialized () {
let { initializedCount, length } = this.get() let { initializedCount, length } = this.get()
initializedCount++ initializedCount++

View file

@ -3,9 +3,17 @@ import {
getNthStatus, getNthStatus,
getNthStatusContent, getNthStatusContent,
getUrl, getUrl,
getNthReplyButton, getNthComposeReplyInput, sleep, getNthReplyButton,
getNthComposeReplyInput,
sleep,
getAriaSetSize, getAriaSetSize,
getNthComposeReplyButton, goBack, goForward getNthComposeReplyButton,
goBack,
goForward,
composeInput,
composeButton,
getNthStatusId,
getStatusContents
} from '../utils' } from '../utils'
import { postAs, postReplyAs } from '../serverActions' import { postAs, postReplyAs } from '../serverActions'
@ -106,3 +114,71 @@ test('reply to non-focused grandchild status in a thread', async t => {
.click(getNthComposeReplyButton(3)) .click(getNthComposeReplyButton(3))
await verifyAriaSetSize(t, '4') await verifyAriaSetSize(t, '4')
}) })
async function replyToNthStatus (t, n, replyText, expectedN) {
await t
.click(getNthReplyButton(n))
.typeText(getNthComposeReplyInput(n), replyText, { paste: true })
.click(getNthComposeReplyButton(n))
.expect(getNthStatusContent(expectedN).innerText).contains(replyText)
}
test('no duplicates in threads', async t => {
await loginAsFoobar(t)
await t
.hover(composeInput)
.typeText(composeInput, 'this is my thread 1', { paste: true })
.click(composeButton)
.expect(getNthStatusContent(1).innerText).contains('this is my thread 1')
.click(getNthStatus(1))
.expect(getUrl()).contains('status')
await replyToNthStatus(t, 1, 'this is my thread 2', 2)
const [id1, id2] = await Promise.all([getNthStatusId(1)(), getNthStatusId(2)()])
await t
.click(getNthStatus(2))
.expect(getUrl()).contains(id2)
await replyToNthStatus(t, 2, 'this is my thread 3', 3)
const id3 = await getNthStatusId(3)()
await postReplyAs('quux', 'hey i am replying to 1', id1)
await postReplyAs('admin', 'hey i am replying to 3', id3)
await t
.expect(getNthStatusContent(4).innerText).contains('hey i am replying to 3', { timeout: 20000 })
const idReplyTo3 = await getNthStatusId(4)()
await t
.click(getNthStatus(4))
.expect(getUrl()).contains(idReplyTo3)
await postReplyAs('quux', 'hey check this reply', id1)
await t
.click(getNthStatus(2))
.expect(getUrl()).contains(id2)
.click(getNthStatus(3))
.expect(getUrl()).contains(id3)
await replyToNthStatus(t, 3, 'this is my thread 4', 5)
await replyToNthStatus(t, 5, 'this is my thread 5', 6)
const id5 = await getNthStatusId(6)()
await postReplyAs('admin', 'hey i am replying to 1 again', id1)
await t
.click(getNthStatus(6))
.expect(getUrl()).contains(id5)
.click(getNthStatus(1))
.expect(getUrl()).contains(id1)
await t
.click(getNthStatus(6))
.expect(getUrl()).contains(id5)
await replyToNthStatus(t, 5, 'this is my thread 6', 6)
await t
.click(getNthStatus(1))
.expect(getUrl()).contains(id1)
const contents = await getStatusContents()
const contentsToCounts = new Map()
for (const content of contents) {
contentsToCounts.set(content, 1 + (contentsToCounts.get(content) || 0))
}
const duplicates = [...contentsToCounts.entries()].filter(arr => arr[1] > 1).map(arr => arr[0])
// there should be no duplicates
await t
.expect(duplicates).eql([])
})

View file

@ -167,6 +167,27 @@ export const getActiveElementInsideNthStatus = exec(() => {
return '' return ''
}) })
export const getNthStatusId = n => exec(() => {
return document.querySelector(getNthStatusSelector(n))
.getAttribute('id')
.split('/')
.slice(-1)[0]
}, {
dependencies: {
getNthStatusSelector,
n
}
})
export const getStatusContents = exec(() => {
const res = []
const elements = document.querySelectorAll('.list-item > article .status-content')
for (let i = 0; i < elements.length; i++) {
res.push(elements[i].innerText)
}
return res
})
export const getTitleText = exec(() => document.head.querySelector('title') && document.head.querySelector('title').innerHTML) export const getTitleText = exec(() => document.head.querySelector('title') && document.head.querySelector('title').innerHTML)
export const goBack = exec(() => window.history.back()) export const goBack = exec(() => window.history.back())