add failing test for offline threads
This commit is contained in:
parent
e3850beddf
commit
53081ffe54
|
@ -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'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]))
|
||||||
|
|
|
@ -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.
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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: {},
|
statusModifications: {},
|
||||||
customEmoji: {},
|
customEmoji: {},
|
||||||
composeData: {},
|
composeData: {},
|
||||||
verifyCredentials: {}
|
verifyCredentials: {},
|
||||||
|
online: !process.browser || navigator.onLine
|
||||||
})
|
})
|
||||||
|
|
||||||
mixins(PinaforeStore)
|
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 { 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'
|
||||||
|
|
|
@ -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'},
|
||||||
|
]
|
|
@ -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()
|
||||||
|
})
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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`)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue