add failing test for offline threads

This commit is contained in:
Nolan Lawson 2018-03-08 18:08:14 -08:00
parent e3850beddf
commit 53081ffe54
14 changed files with 207 additions and 55 deletions

View File

@ -1,5 +1,28 @@
import times from 'lodash/times' import times from 'lodash/times'
function unrollThread(user, prefix, privacy, thread) {
let res = []
function unroll(node, parentKey) {
if (!node) {
return
}
for (let key of Object.keys(node)) {
res.push({
user: user,
post: {
internalId: prefix + key,
text: key,
inReplyTo: parentKey && (prefix + parentKey)
}
})
unroll(node[key], key)
}
}
unroll(thread)
return res
}
export const actions = times(30, i => ({ export const actions = times(30, i => ({
post: { post: {
text: '' + (i + 1) text: '' + (i + 1)
@ -260,4 +283,44 @@ export const actions = times(30, i => ({
privacy: 'unlisted' privacy: 'unlisted'
} }
} }
]) ].concat(unrollThread('baz', 'bazthread-', 'unlisted', {
'thread 1' : {
'thread 2': {
'thread 2a': null,
'thread 2b': {
'thread 2b1': null
},
'thread 2c': null
},
'thread 3': {
'thread 3a': null,
'thread 3b': null,
'thread 3c': null
}
}
})).concat([
{
user: 'baz',
post: {
internalId: 'bazthread-thread 2b2',
text: 'thread 2b2',
inReplyTo: 'bazthread-thread 2b'
}
},
{
user: 'baz',
post: {
internalId: 'bazthread-thread 2d',
text: 'thread 2d',
inReplyTo: 'bazthread-thread 2'
}
},
{
user: 'baz',
post: {
internalId: 'bazthread-thread 2b2a',
text: 'thread 2b2a',
inReplyTo: 'bazthread-thread 2b2'
}
},
]))

View File

@ -53,8 +53,7 @@ async function setupMastodonDatabase () {
async function runMastodon () { async function runMastodon () {
console.log('Running mastodon...') console.log('Running mastodon...')
let cmds = [ let cmds = [
'gem install bundler', 'gem install bundler foreman',
'gem install foreman',
'bundle install', 'bundle install',
'yarn --pure-lockfile' 'yarn --pure-lockfile'
] ]

Binary file not shown.

Binary file not shown.

View File

@ -1,4 +1,3 @@
<:Window bind:online />
<Nav :page /> <Nav :page />
<div class="container"> <div class="container">
@ -6,29 +5,28 @@
<slot></slot> <slot></slot>
</main> </main>
{{#if !$isUserLoggedIn && page === 'home'}} {{#if !$isUserLoggedIn && page === 'home'}}
<footer> <HiddenFromSSR>
<p> <footer>
Pinafore is <ExternalLink href="https://github.com/nolanlawson/pinafore">open-source software</ExternalLink> <p>
created by <ExternalLink href="https://nolanlawson.com">Nolan Lawson</ExternalLink> and distributed under the Pinafore is <ExternalLink href="https://github.com/nolanlawson/pinafore">open-source software</ExternalLink>
<ExternalLink href="https://github.com/nolanlawson/pinafore/blob/master/LICENSE">AGPL License</ExternalLink>. created by <ExternalLink href="https://nolanlawson.com">Nolan Lawson</ExternalLink> and distributed under the
</p> <ExternalLink href="https://github.com/nolanlawson/pinafore/blob/master/LICENSE">AGPL License</ExternalLink>.
</footer> </p>
</footer>
</HiddenFromSSR>
{{/if}} {{/if}}
</div> </div>
<script> <script>
import Nav from './Nav.html'; import Nav from './Nav.html';
import { store } from '../_store/store' import { store } from '../_store/store'
import ExternalLink from './ExternalLink.html' import ExternalLink from './ExternalLink.html'
import HiddenFromSSR from './HiddenFromSSR.html'
export default { export default {
oncreate() {
this.observe('online', online => {
this.store.set({online: online})
})
},
components: { components: {
Nav, Nav,
ExternalLink ExternalLink,
HiddenFromSSR
}, },
store: () => store store: () => store
} }

View File

@ -1,9 +1,11 @@
import { instanceObservers } from './instanceObservers' import { instanceObservers } from './instanceObservers'
import { timelineObservers } from './timelineObservers' import { timelineObservers } from './timelineObservers'
import { notificationObservers } from './notificationObservers' import { notificationObservers } from './notificationObservers'
import { onlineObservers } from './onlineObservers'
export function observers (store) { export function observers (store) {
instanceObservers(store) instanceObservers(store)
timelineObservers(store) timelineObservers(store)
notificationObservers(store) notificationObservers(store)
onlineObservers(store)
} }

View File

@ -0,0 +1,33 @@
import debounce from 'lodash/debounce'
import { toast } from '../../_utils/toast'
const OFFLINE_DELAY = 1000
const notifyOffline = debounce(() => {
toast.say('You seem to be offline. You can still read toots while offline.')
}, OFFLINE_DELAY)
export function onlineObservers(store) {
if (!process.browser) {
return
}
let meta = document.getElementById('theThemeColor')
let oldTheme = meta.content
store.observe('online', online => {
document.body.classList.toggle('offline', !online)
if (online) {
meta.content = oldTheme
} else {
let offlineThemeColor = window.__themeColors.offline
if (meta.content !== offlineThemeColor) {
oldTheme = meta.content
}
meta.content = offlineThemeColor
notifyOffline()
}
})
window.addEventListener('offline', () => store.set({online: false}))
window.addEventListener('online', () => store.set({online: true}))
}

View File

@ -41,7 +41,8 @@ export const store = new PinaforeStore({
statusModifications: {}, statusModifications: {},
customEmoji: {}, customEmoji: {},
composeData: {}, composeData: {},
verifyCredentials: {} verifyCredentials: {},
online: !process.browser || navigator.onLine
}) })
mixins(PinaforeStore) mixins(PinaforeStore)

View File

@ -1,33 +0,0 @@
import debounce from 'lodash/debounce'
import { toast } from './toast'
const OFFLINE_DELAY = 1000
const notifyOffline = debounce(() => {
toast.say('You seem to be offline. You can still read toots while offline.')
}, OFFLINE_DELAY)
let oldTheme
let meta = process.browser && document.querySelector('meta[name="theme-color"]')
const observe = online => {
if (!localStorage.store_currentInstance) {
return // only show notification for logged-in users
}
document.body.classList.toggle('offline', !online)
if (online) {
meta.content = oldTheme || window.__themeColors['default']
} else {
let offlineThemeColor = window.__themeColors.offline
if (meta.content !== offlineThemeColor) { oldTheme = meta.content }
meta.content = offlineThemeColor
notifyOffline()
}
}
if (!navigator.onLine) {
observe(false)
}
window.addEventListener('offline', () => observe(false))
window.addEventListener('online', () => observe(true))

View File

@ -1,6 +1,5 @@
import { init } from 'sapper/runtime.js' import { init } from 'sapper/runtime.js'
import { loadPolyfills } from '../routes/_utils/loadPolyfills' import { loadPolyfills } from '../routes/_utils/loadPolyfills'
import '../routes/_utils/offlineNotification'
import '../routes/_utils/serviceWorkerClient' import '../routes/_utils/serviceWorkerClient'
import '../routes/_utils/historyEvents' import '../routes/_utils/historyEvents'
import '../routes/_utils/loadingMask' import '../routes/_utils/loadingMask'

View File

@ -59,3 +59,31 @@ export const quuxStatuses = [
].concat(times(25, i => ({content: `unlisted thread ${25 - i}`}))) ].concat(times(25, i => ({content: `unlisted thread ${25 - i}`})))
export const quuxThread = times(25, i => ({content: `unlisted thread ${i + 1}`})) export const quuxThread = times(25, i => ({content: `unlisted thread ${i + 1}`}))
export const bazThreadRelativeTo2B2 = [
{content: 'thread 1'},
{content: 'thread 2'},
{content: 'thread 2b'},
{content: 'thread 2b2'},
{content: 'thread 2b2a'}
]
export const bazThreadRelativeTo2b = [
{content: 'thread 1'},
{content: 'thread 2'},
{content: 'thread 2b'},
{content: 'thread 2b1'},
{content: 'thread 2b2'},
{content: 'thread 2b2a'}
]
export const bazThreadRelativeTo2 = [
{content: 'thread 1'},
{content: 'thread 2'},
{content: 'thread 2a'},
{content: 'thread 2b'},
{content: 'thread 2b1'},
{content: 'thread 2b2'},
{content: 'thread 2b2a'},
{content: 'thread 2c'},
]

View File

@ -1,11 +1,14 @@
import { Selector as $ } from 'testcafe' import { Selector as $ } from 'testcafe'
import { getNthStatus, getUrl, validateTimeline, scrollToBottomOfTimeline } from '../utils' import {
getNthStatus, getUrl, validateTimeline, scrollToBottomOfTimeline, getFirstVisibleStatus,
goBack, forceOffline, forceOnline, homeNavButton, searchNavButton, searchInput, getNthSearchResult
} from '../utils'
import { foobarRole } from '../roles' import { foobarRole } from '../roles'
import { quuxThread } from '../fixtures' import { bazThreadRelativeTo2, bazThreadRelativeTo2b, bazThreadRelativeTo2B2, quuxThread } from '../fixtures'
fixture`009-threads.js` fixture`009-threads.js`
.page`http://localhost:4002` .page`http://localhost:4002`
/*
test('Shows a thread', async t => { test('Shows a thread', async t => {
await t.useRole(foobarRole) await t.useRole(foobarRole)
.click($('a').withText('quux')) .click($('a').withText('quux'))
@ -35,3 +38,46 @@ test('Scrolls to proper point in thread', async t => {
.expect(Math.round(getNthStatus(16).boundingClientRect.top)) .expect(Math.round(getNthStatus(16).boundingClientRect.top))
.eql(Math.round($('.container').boundingClientRect.top)) .eql(Math.round($('.container').boundingClientRect.top))
}) })
*/
async function navigateToBazAccount(t) {
await t.click(searchNavButton)
.expect(getUrl()).contains('/search')
.typeText(searchInput, 'baz', {paste: true})
.pressKey('enter')
.click(getNthSearchResult(1))
.expect(getUrl()).contains('/accounts/5')
}
async function validateForkedThread(t) {
await t.hover(getNthStatus(1))
.click(getNthStatus(2))
.expect(getUrl()).contains('/statuses')
await validateTimeline(t, bazThreadRelativeTo2B2)
await goBack()
await t.hover(getNthStatus(3))
.hover(getNthStatus(5))
.hover(getNthStatus(7))
.hover(getNthStatus(9))
.click(getNthStatus(9))
.expect(getUrl()).contains('/statuses')
await validateTimeline(t, bazThreadRelativeTo2b)
await goBack()
await t.hover(getNthStatus(11))
.click(getNthStatus(11))
.expect(getUrl()).contains('/statuses')
await validateTimeline(t, bazThreadRelativeTo2)
}
test('Forked threads look correct online and offline', async t => {
await t.useRole(foobarRole)
.hover(getFirstVisibleStatus())
await navigateToBazAccount(t)
await validateForkedThread(t)
await t.navigateTo('/')
.hover(getFirstVisibleStatus())
await navigateToBazAccount(t)
await forceOffline()
await validateForkedThread(t)
await forceOnline()
})

View File

@ -22,5 +22,11 @@ export const users = {
password: 'ExternalLinksExternalLink', password: 'ExternalLinksExternalLink',
accessToken: 'e9a463ba1729ae0049a97a312af702cb3d08d84de1cc8d6da3fad90af068117b', accessToken: 'e9a463ba1729ae0049a97a312af702cb3d08d84de1cc8d6da3fad90af068117b',
id: 4 id: 4
},
baz: {
username: 'baz',
password: 'bazbazbaz',
accessToken: '0639238783efdfde849304bc89ec0c4b60b5ef5f261f60859fcd597de081cfdc',
id: 5
} }
} }

View File

@ -10,6 +10,7 @@ export const modalDialogContents = $('.modal-dialog-contents')
export const closeDialogButton = $('.close-dialog-button') export const closeDialogButton = $('.close-dialog-button')
export const notificationsNavButton = $('nav a[href="/notifications"]') export const notificationsNavButton = $('nav a[href="/notifications"]')
export const homeNavButton = $('nav a[href="/"]') export const homeNavButton = $('nav a[href="/"]')
export const searchNavButton = $('nav a[href="/search"]')
export const formError = $('.form-error-user-error') export const formError = $('.form-error-user-error')
export const composeInput = $('.compose-box-input') export const composeInput = $('.compose-box-input')
export const composeContentWarning = $('.content-warning-input') export const composeContentWarning = $('.content-warning-input')
@ -23,6 +24,7 @@ export const emailInput = $('input#user_email')
export const passwordInput = $('input#user_password') export const passwordInput = $('input#user_password')
export const authorizeInput = $('button[type=submit]:not(.negative)') export const authorizeInput = $('button[type=submit]:not(.negative)')
export const logInToInstanceLink = $('a[href="/settings/instances/add"]') export const logInToInstanceLink = $('a[href="/settings/instances/add"]')
export const searchInput = $('.search-input')
export const favoritesCountElement = $('.status-favs-reblogs:nth-child(3)').addCustomDOMProperties({ export const favoritesCountElement = $('.status-favs-reblogs:nth-child(3)').addCustomDOMProperties({
innerCount: el => parseInt(el.innerText, 10) innerCount: el => parseInt(el.innerText, 10)
@ -40,6 +42,10 @@ export const getActiveElementClass = exec(() =>
export const goBack = exec(() => window.history.back()) export const goBack = exec(() => window.history.back())
export const forceOffline = exec(() => window.store.set({online: false}))
export const forceOnline = exec(() => window.store.set({online: true}))
export const getComposeSelectionStart = exec(() => composeInput().selectionStart, { export const getComposeSelectionStart = exec(() => composeInput().selectionStart, {
dependencies: { composeInput } dependencies: { composeInput }
}) })
@ -57,6 +63,10 @@ export const uploadKittenImage = i => (exec(() => {
} }
})) }))
export function getNthSearchResult (n) {
return $(`.search-result:nth-child(${n}) a`)
}
export function getNthMedia (n) { export function getNthMedia (n) {
return $(`.compose-media:nth-child(${n}) img`) return $(`.compose-media:nth-child(${n}) img`)
} }