diff --git a/tests/fixtures.js b/tests/fixtures.js index 7ac46d5..c4f842d 100644 --- a/tests/fixtures.js +++ b/tests/fixtures.js @@ -57,3 +57,5 @@ export const quuxStatuses = [ {content: 'pinned toot 2'}, {content: 'pinned toot 1'} ].concat(times(25, i => ({content: `unlisted thread ${25 - i}`}))) + +export const quuxThread = times(25, i => ({content: `unlisted thread ${i + 1}`})) \ No newline at end of file diff --git a/tests/spec/05-status-types.js b/tests/spec/05-status-types.js index 341631b..efaa20e 100644 --- a/tests/spec/05-status-types.js +++ b/tests/spec/05-status-types.js @@ -1,4 +1,4 @@ -import { getNthVirtualArticle } from '../utils' +import { getNthStatus } from '../utils' import { foobarRole } from '../roles' fixture`05-status-types.js` @@ -6,39 +6,39 @@ fixture`05-status-types.js` test('shows direct vs followers-only vs regular', async t => { await t.useRole(foobarRole) - .expect(getNthVirtualArticle(1).getAttribute('aria-label')).eql('Status by admin') - .expect(getNthVirtualArticle(1).find('.status-content').innerText).contains('notification of unlisted message') - .expect(getNthVirtualArticle(1).find('.status-toolbar button:nth-child(2)').getAttribute('aria-label')) + .expect(getNthStatus(1).getAttribute('aria-label')).eql('Status by admin') + .expect(getNthStatus(1).find('.status-content').innerText).contains('notification of unlisted message') + .expect(getNthStatus(1).find('.status-toolbar button:nth-child(2)').getAttribute('aria-label')) .eql('Boost') - .expect(getNthVirtualArticle(1).find('.status-toolbar button:nth-child(2)').hasAttribute('disabled')).notOk() - .expect(getNthVirtualArticle(2).getAttribute('aria-label')).eql('Status by admin') - .expect(getNthVirtualArticle(2).find('.status-content').innerText).contains('notification of followers-only message') - .expect(getNthVirtualArticle(2).find('.status-toolbar button:nth-child(2)').getAttribute('aria-label')) + .expect(getNthStatus(1).find('.status-toolbar button:nth-child(2)').hasAttribute('disabled')).notOk() + .expect(getNthStatus(2).getAttribute('aria-label')).eql('Status by admin') + .expect(getNthStatus(2).find('.status-content').innerText).contains('notification of followers-only message') + .expect(getNthStatus(2).find('.status-toolbar button:nth-child(2)').getAttribute('aria-label')) .eql('Cannot be boosted because this is followers-only') - .expect(getNthVirtualArticle(2).find('.status-toolbar button:nth-child(2)').hasAttribute('disabled')).ok() - .expect(getNthVirtualArticle(3).getAttribute('aria-label')).eql('Direct message by admin') - .expect(getNthVirtualArticle(3).find('.status-content').innerText).contains('notification of direct message') - .expect(getNthVirtualArticle(3).find('.status-toolbar button:nth-child(2)').getAttribute('aria-label')) + .expect(getNthStatus(2).find('.status-toolbar button:nth-child(2)').hasAttribute('disabled')).ok() + .expect(getNthStatus(3).getAttribute('aria-label')).eql('Direct message by admin') + .expect(getNthStatus(3).find('.status-content').innerText).contains('notification of direct message') + .expect(getNthStatus(3).find('.status-toolbar button:nth-child(2)').getAttribute('aria-label')) .eql('Cannot be boosted because this is a direct message') - .expect(getNthVirtualArticle(3).find('.status-toolbar button:nth-child(2)').hasAttribute('disabled')).ok() + .expect(getNthStatus(3).find('.status-toolbar button:nth-child(2)').hasAttribute('disabled')).ok() }) test('shows direct vs followers-only vs regular in notifications', async t => { await t.useRole(foobarRole) .navigateTo('/notifications') - .expect(getNthVirtualArticle(2).getAttribute('aria-label')).eql('Status by admin') - .expect(getNthVirtualArticle(2).find('.status-content').innerText).contains('notification of unlisted message') - .expect(getNthVirtualArticle(2).find('.status-toolbar button:nth-child(2)').getAttribute('aria-label')) + .expect(getNthStatus(2).getAttribute('aria-label')).eql('Status by admin') + .expect(getNthStatus(2).find('.status-content').innerText).contains('notification of unlisted message') + .expect(getNthStatus(2).find('.status-toolbar button:nth-child(2)').getAttribute('aria-label')) .eql('Boost') - .expect(getNthVirtualArticle(2).find('.status-toolbar button:nth-child(2)').hasAttribute('disabled')).notOk() - .expect(getNthVirtualArticle(3).getAttribute('aria-label')).eql('Status by admin') - .expect(getNthVirtualArticle(3).find('.status-content').innerText).contains('notification of followers-only message') - .expect(getNthVirtualArticle(3).find('.status-toolbar button:nth-child(2)').getAttribute('aria-label')) + .expect(getNthStatus(2).find('.status-toolbar button:nth-child(2)').hasAttribute('disabled')).notOk() + .expect(getNthStatus(3).getAttribute('aria-label')).eql('Status by admin') + .expect(getNthStatus(3).find('.status-content').innerText).contains('notification of followers-only message') + .expect(getNthStatus(3).find('.status-toolbar button:nth-child(2)').getAttribute('aria-label')) .eql('Cannot be boosted because this is followers-only') - .expect(getNthVirtualArticle(3).find('.status-toolbar button:nth-child(2)').hasAttribute('disabled')).ok() - .expect(getNthVirtualArticle(4).getAttribute('aria-label')).eql('Direct message by admin') - .expect(getNthVirtualArticle(4).find('.status-content').innerText).contains('notification of direct message') - .expect(getNthVirtualArticle(4).find('.status-toolbar button:nth-child(2)').getAttribute('aria-label')) + .expect(getNthStatus(3).find('.status-toolbar button:nth-child(2)').hasAttribute('disabled')).ok() + .expect(getNthStatus(4).getAttribute('aria-label')).eql('Direct message by admin') + .expect(getNthStatus(4).find('.status-content').innerText).contains('notification of direct message') + .expect(getNthStatus(4).find('.status-toolbar button:nth-child(2)').getAttribute('aria-label')) .eql('Cannot be boosted because this is a direct message') - .expect(getNthVirtualArticle(4).find('.status-toolbar button:nth-child(2)').hasAttribute('disabled')).ok() + .expect(getNthStatus(4).find('.status-toolbar button:nth-child(2)').hasAttribute('disabled')).ok() }) diff --git a/tests/spec/06-tabindex.js b/tests/spec/06-tabindex.js index dae96e5..00d3553 100644 --- a/tests/spec/06-tabindex.js +++ b/tests/spec/06-tabindex.js @@ -1,5 +1,5 @@ import { Selector as $ } from 'testcafe' -import { getNthVirtualArticle } from '../utils' +import { getNthStatus } from '../utils' import { foobarRole } from '../roles' fixture`06-tabindex.js` @@ -7,27 +7,27 @@ fixture`06-tabindex.js` test('shows correct tabindex in home timeline', async t => { await t.useRole(foobarRole) - .expect(getNthVirtualArticle(0).getAttribute('tabindex')).eql('0') - .expect(getNthVirtualArticle(1).getAttribute('tabindex')).eql('0') - .expect(getNthVirtualArticle(2).getAttribute('tabindex')).eql('0') - .expect(getNthVirtualArticle(3).getAttribute('tabindex')).eql('0') + .expect(getNthStatus(0).getAttribute('tabindex')).eql('0') + .expect(getNthStatus(1).getAttribute('tabindex')).eql('0') + .expect(getNthStatus(2).getAttribute('tabindex')).eql('0') + .expect(getNthStatus(3).getAttribute('tabindex')).eql('0') }) test('shows correct tabindex in notifications', async t => { await t.useRole(foobarRole) .navigateTo('/notifications') - .expect(getNthVirtualArticle(0).getAttribute('tabindex')).eql('0') - .expect(getNthVirtualArticle(1).getAttribute('tabindex')).eql('0') - .expect(getNthVirtualArticle(2).getAttribute('tabindex')).eql('0') - .hover(getNthVirtualArticle(2)) - .expect(getNthVirtualArticle(3).getAttribute('tabindex')).eql('0') - .expect(getNthVirtualArticle(4).getAttribute('tabindex')).eql('0') - .hover(getNthVirtualArticle(4)) - .expect(getNthVirtualArticle(5).getAttribute('tabindex')).eql('0') - .expect(getNthVirtualArticle(6).getAttribute('tabindex')).eql('0') - .hover(getNthVirtualArticle(6)) - .expect(getNthVirtualArticle(7).getAttribute('tabindex')).eql('0') - .expect(getNthVirtualArticle(7).getAttribute('aria-setsize')).eql('8') + .expect(getNthStatus(0).getAttribute('tabindex')).eql('0') + .expect(getNthStatus(1).getAttribute('tabindex')).eql('0') + .expect(getNthStatus(2).getAttribute('tabindex')).eql('0') + .hover(getNthStatus(2)) + .expect(getNthStatus(3).getAttribute('tabindex')).eql('0') + .expect(getNthStatus(4).getAttribute('tabindex')).eql('0') + .hover(getNthStatus(4)) + .expect(getNthStatus(5).getAttribute('tabindex')).eql('0') + .expect(getNthStatus(6).getAttribute('tabindex')).eql('0') + .hover(getNthStatus(6)) + .expect(getNthStatus(7).getAttribute('tabindex')).eql('0') + .expect(getNthStatus(7).getAttribute('aria-setsize')).eql('8') }) test('shows correct tabindex in pinned statuses', async t => { diff --git a/tests/spec/08-status-media.js b/tests/spec/08-status-media.js index fc3df5e..f10a325 100644 --- a/tests/spec/08-status-media.js +++ b/tests/spec/08-status-media.js @@ -1,5 +1,5 @@ import { Selector as $ } from 'testcafe' -import { getNthVirtualArticle } from '../utils' +import { getNthStatus } from '../utils' import { foobarRole } from '../roles' const modalDialogContents = $('.modal-dialog-contents') @@ -10,33 +10,33 @@ fixture`08-status-media.js` test('shows sensitive images and videos', async t => { await t.useRole(foobarRole) - .hover(getNthVirtualArticle(3)) - .hover(getNthVirtualArticle(6)) - .hover(getNthVirtualArticle(7)) - .expect(getNthVirtualArticle(7).find('.status-media img').exists).notOk() - .click(getNthVirtualArticle(7).find('.status-sensitive-media-button')) - .expect(getNthVirtualArticle(7).find('.status-media img').getAttribute('alt')).eql('kitten') - .expect(getNthVirtualArticle(7).find('.status-media img').hasAttribute('src')).ok() - .hover(getNthVirtualArticle(8)) - .expect(getNthVirtualArticle(8).find('.status-media .play-video-button').exists).notOk() - .click(getNthVirtualArticle(8).find('.status-sensitive-media-button')) - .expect(getNthVirtualArticle(8).find('.status-media .play-video-button').exists).ok() + .hover(getNthStatus(3)) + .hover(getNthStatus(6)) + .hover(getNthStatus(7)) + .expect(getNthStatus(7).find('.status-media img').exists).notOk() + .click(getNthStatus(7).find('.status-sensitive-media-button')) + .expect(getNthStatus(7).find('.status-media img').getAttribute('alt')).eql('kitten') + .expect(getNthStatus(7).find('.status-media img').hasAttribute('src')).ok() + .hover(getNthStatus(8)) + .expect(getNthStatus(8).find('.status-media .play-video-button').exists).notOk() + .click(getNthStatus(8).find('.status-sensitive-media-button')) + .expect(getNthStatus(8).find('.status-media .play-video-button').exists).ok() }) test('click and close image and video modals', async t => { await t.useRole(foobarRole) - .hover(getNthVirtualArticle(3)) - .hover(getNthVirtualArticle(6)) - .hover(getNthVirtualArticle(7)) - .hover(getNthVirtualArticle(9)) + .hover(getNthStatus(3)) + .hover(getNthStatus(6)) + .hover(getNthStatus(7)) + .hover(getNthStatus(9)) .expect(modalDialogContents.exists).notOk() - .click(getNthVirtualArticle(9).find('.play-video-button')) + .click(getNthStatus(9).find('.play-video-button')) .expect(modalDialogContents.exists).ok() .click(closeDialogButton) .expect(modalDialogContents.exists).notOk() - .hover(getNthVirtualArticle(11)) - .hover(getNthVirtualArticle(12)) - .click(getNthVirtualArticle(12).find('.show-image-button')) + .hover(getNthStatus(11)) + .hover(getNthStatus(12)) + .click(getNthStatus(12).find('.show-image-button')) .expect(modalDialogContents.exists).ok() .click(closeDialogButton) .expect(modalDialogContents.exists).notOk() diff --git a/tests/spec/09-threads.js b/tests/spec/09-threads.js new file mode 100644 index 0000000..f86d634 --- /dev/null +++ b/tests/spec/09-threads.js @@ -0,0 +1,37 @@ +import { Selector as $ } from 'testcafe' +import { getNthStatus, getUrl, validateTimeline, scrollToBottomOfTimeline } from '../utils' +import { foobarRole } from '../roles' +import { quuxThread } from '../fixtures' + +fixture`09-threads.js` + .page`http://localhost:4002` + +test('Shows a thread', async t => { + await t.useRole(foobarRole) + .click($('a').withText('quux')) + + await scrollToBottomOfTimeline(t) + await t + .click(getNthStatus(26)) + .expect(getUrl()).contains('/statuses/99549257018049016') + + await validateTimeline(t, quuxThread) + + await t.expect(getNthStatus(24).getAttribute('aria-setsize')).eql('25') +}) + +test('Scrolls to proper point in thread', async t => { + await t.useRole(foobarRole) + .click($('a').withText('quux')) + .hover(getNthStatus(0)) + .hover(getNthStatus(2)) + .hover(getNthStatus(4)) + .hover(getNthStatus(6)) + .hover(getNthStatus(8)) + .hover(getNthStatus(10)) + .click(getNthStatus(10)) + .expect(getUrl()).contains('/statuses/99549263341916700') + .expect(getNthStatus(16).innerText).contains('unlisted thread 17') + .expect(Math.round(getNthStatus(16).boundingClientRect.top)) + .eql(Math.round($('.container').boundingClientRect.top)) +}) \ No newline at end of file diff --git a/tests/utils.js b/tests/utils.js index 4965f8d..4b884cf 100644 --- a/tests/utils.js +++ b/tests/utils.js @@ -6,38 +6,55 @@ export const addInstanceButton = $('.add-new-instance button') export const getUrl = exec(() => window.location.href) -export function getNthVirtualArticle (n) { - return $(`.virtual-list-item[aria-hidden="false"] article[aria-posinset="${n}"]`) +export function getNthStatus (n) { + return $(`[aria-hidden="false"] > article[aria-posinset="${n}"]`) +} + +export function getLastVisibleStatus () { + return $(`[aria-hidden="false"] > article[aria-posinset]`).nth(-1) } export async function validateTimeline (t, timeline) { for (let i = 0; i < timeline.length; i++) { let status = timeline[i] if (status.content) { - await t.expect(getNthVirtualArticle(i).find('.status-content p').innerText) + await t.expect(getNthStatus(i).find('.status-content p').innerText) .contains(status.content) } if (status.spoiler) { - await t.expect(getNthVirtualArticle(i).find('.status-spoiler p').innerText) + await t.expect(getNthStatus(i).find('.status-spoiler p').innerText) .contains(status.spoiler) } if (status.followedBy) { - await t.expect(getNthVirtualArticle(i).find('.status-header span').innerText) + await t.expect(getNthStatus(i).find('.status-header span').innerText) .contains(status.followedBy + ' followed you') } if (status.rebloggedBy) { - await t.expect(getNthVirtualArticle(i).find('.status-header span').innerText) + await t.expect(getNthStatus(i).find('.status-header span').innerText) .contains(status.rebloggedBy + ' boosted your status') } if (status.favoritedBy) { - await t.expect(getNthVirtualArticle(i).find('.status-header span').innerText) + await t.expect(getNthStatus(i).find('.status-header span').innerText) .contains(status.favoritedBy + ' favorited your status') } // hovering forces TestCafé to scroll to that element: https://git.io/vABV2 if (i % 3 === 2) { // only scroll every nth element - await t.hover(getNthVirtualArticle(i)) - await t.expect($('.loading-footer').exist).notOk() + await t.hover(getNthStatus(i)) + .expect($('.loading-footer').exist).notOk() } } } + +export async function scrollToBottomOfTimeline (t) { + let lastSize = null + while (true) { + await t.hover(getLastVisibleStatus()) + .expect($('.loading-footer').exist).notOk() + let newSize = await getLastVisibleStatus().getAttribute('aria-setsize') + if (newSize === lastSize) { + break + } + lastSize = newSize + } +} \ No newline at end of file