add failing test for offline threads
This commit is contained in:
parent
e3850beddf
commit
53081ffe54
|
@ -1,5 +1,28 @@
|
|||
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 => ({
|
||||
post: {
|
||||
text: '' + (i + 1)
|
||||
|
@ -260,4 +283,44 @@ export const actions = times(30, i => ({
|
|||
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'
|
||||
}
|
||||
},
|
||||
]))
|
||||
|
|
|
@ -53,8 +53,7 @@ async function setupMastodonDatabase () {
|
|||
async function runMastodon () {
|
||||
console.log('Running mastodon...')
|
||||
let cmds = [
|
||||
'gem install bundler',
|
||||
'gem install foreman',
|
||||
'gem install bundler foreman',
|
||||
'bundle install',
|
||||
'yarn --pure-lockfile'
|
||||
]
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -1,4 +1,3 @@
|
|||
<:Window bind:online />
|
||||
<Nav :page />
|
||||
|
||||
<div class="container">
|
||||
|
@ -6,29 +5,28 @@
|
|||
<slot></slot>
|
||||
</main>
|
||||
{{#if !$isUserLoggedIn && page === 'home'}}
|
||||
<footer>
|
||||
<p>
|
||||
Pinafore is <ExternalLink href="https://github.com/nolanlawson/pinafore">open-source software</ExternalLink>
|
||||
created by <ExternalLink href="https://nolanlawson.com">Nolan Lawson</ExternalLink> and distributed under the
|
||||
<ExternalLink href="https://github.com/nolanlawson/pinafore/blob/master/LICENSE">AGPL License</ExternalLink>.
|
||||
</p>
|
||||
</footer>
|
||||
<HiddenFromSSR>
|
||||
<footer>
|
||||
<p>
|
||||
Pinafore is <ExternalLink href="https://github.com/nolanlawson/pinafore">open-source software</ExternalLink>
|
||||
created by <ExternalLink href="https://nolanlawson.com">Nolan Lawson</ExternalLink> and distributed under the
|
||||
<ExternalLink href="https://github.com/nolanlawson/pinafore/blob/master/LICENSE">AGPL License</ExternalLink>.
|
||||
</p>
|
||||
</footer>
|
||||
</HiddenFromSSR>
|
||||
{{/if}}
|
||||
</div>
|
||||
<script>
|
||||
import Nav from './Nav.html';
|
||||
import { store } from '../_store/store'
|
||||
import ExternalLink from './ExternalLink.html'
|
||||
import HiddenFromSSR from './HiddenFromSSR.html'
|
||||
|
||||
export default {
|
||||
oncreate() {
|
||||
this.observe('online', online => {
|
||||
this.store.set({online: online})
|
||||
})
|
||||
},
|
||||
components: {
|
||||
Nav,
|
||||
ExternalLink
|
||||
ExternalLink,
|
||||
HiddenFromSSR
|
||||
},
|
||||
store: () => store
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import { instanceObservers } from './instanceObservers'
|
||||
import { timelineObservers } from './timelineObservers'
|
||||
import { notificationObservers } from './notificationObservers'
|
||||
import { onlineObservers } from './onlineObservers'
|
||||
|
||||
export function observers (store) {
|
||||
instanceObservers(store)
|
||||
timelineObservers(store)
|
||||
notificationObservers(store)
|
||||
onlineObservers(store)
|
||||
}
|
||||
|
|
33
routes/_store/observers/onlineObservers.js
Normal file
33
routes/_store/observers/onlineObservers.js
Normal 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}))
|
||||
}
|
|
@ -41,7 +41,8 @@ export const store = new PinaforeStore({
|
|||
statusModifications: {},
|
||||
customEmoji: {},
|
||||
composeData: {},
|
||||
verifyCredentials: {}
|
||||
verifyCredentials: {},
|
||||
online: !process.browser || navigator.onLine
|
||||
})
|
||||
|
||||
mixins(PinaforeStore)
|
||||
|
|
|
@ -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))
|
|
@ -1,6 +1,5 @@
|
|||
import { init } from 'sapper/runtime.js'
|
||||
import { loadPolyfills } from '../routes/_utils/loadPolyfills'
|
||||
import '../routes/_utils/offlineNotification'
|
||||
import '../routes/_utils/serviceWorkerClient'
|
||||
import '../routes/_utils/historyEvents'
|
||||
import '../routes/_utils/loadingMask'
|
||||
|
|
|
@ -59,3 +59,31 @@ export const quuxStatuses = [
|
|||
].concat(times(25, i => ({content: `unlisted thread ${25 - i}`})))
|
||||
|
||||
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'},
|
||||
]
|
|
@ -1,11 +1,14 @@
|
|||
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 { quuxThread } from '../fixtures'
|
||||
import { bazThreadRelativeTo2, bazThreadRelativeTo2b, bazThreadRelativeTo2B2, quuxThread } from '../fixtures'
|
||||
|
||||
fixture`009-threads.js`
|
||||
.page`http://localhost:4002`
|
||||
|
||||
/*
|
||||
test('Shows a thread', async t => {
|
||||
await t.useRole(foobarRole)
|
||||
.click($('a').withText('quux'))
|
||||
|
@ -35,3 +38,46 @@ test('Scrolls to proper point in thread', async t => {
|
|||
.expect(Math.round(getNthStatus(16).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()
|
||||
})
|
|
@ -22,5 +22,11 @@ export const users = {
|
|||
password: 'ExternalLinksExternalLink',
|
||||
accessToken: 'e9a463ba1729ae0049a97a312af702cb3d08d84de1cc8d6da3fad90af068117b',
|
||||
id: 4
|
||||
},
|
||||
baz: {
|
||||
username: 'baz',
|
||||
password: 'bazbazbaz',
|
||||
accessToken: '0639238783efdfde849304bc89ec0c4b60b5ef5f261f60859fcd597de081cfdc',
|
||||
id: 5
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ export const modalDialogContents = $('.modal-dialog-contents')
|
|||
export const closeDialogButton = $('.close-dialog-button')
|
||||
export const notificationsNavButton = $('nav a[href="/notifications"]')
|
||||
export const homeNavButton = $('nav a[href="/"]')
|
||||
export const searchNavButton = $('nav a[href="/search"]')
|
||||
export const formError = $('.form-error-user-error')
|
||||
export const composeInput = $('.compose-box-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 authorizeInput = $('button[type=submit]:not(.negative)')
|
||||
export const logInToInstanceLink = $('a[href="/settings/instances/add"]')
|
||||
export const searchInput = $('.search-input')
|
||||
|
||||
export const favoritesCountElement = $('.status-favs-reblogs:nth-child(3)').addCustomDOMProperties({
|
||||
innerCount: el => parseInt(el.innerText, 10)
|
||||
|
@ -40,6 +42,10 @@ export const getActiveElementClass = exec(() =>
|
|||
|
||||
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, {
|
||||
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) {
|
||||
return $(`.compose-media:nth-child(${n}) img`)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue