diff --git a/bin/run-mastodon.js b/bin/run-mastodon.js index 16999f46..f7a5967d 100644 --- a/bin/run-mastodon.js +++ b/bin/run-mastodon.js @@ -14,7 +14,7 @@ const writeFile = pify(fs.writeFile.bind(fs)) const dir = __dirname const GIT_URL = 'https://github.com/tootsuite/mastodon.git' -const GIT_TAG = 'v2.5.0' +const GIT_TAG = 'v2.6.1' const DB_NAME = 'pinafore_development' const DB_USER = 'pinafore' diff --git a/bin/setup-mastodon-in-travis.sh b/bin/setup-mastodon-in-travis.sh index bb3b26c6..c67a5d5b 100755 --- a/bin/setup-mastodon-in-travis.sh +++ b/bin/setup-mastodon-in-travis.sh @@ -8,8 +8,8 @@ fi # install ruby source "$HOME/.rvm/scripts/rvm" -rvm install 2.5.1 -rvm use 2.5.1 +rvm install 2.5.3 +rvm use 2.5.3 # fix for redis IPv6 issue # https://travis-ci.community/t/trusty-environment-redis-server-not-starting-with-redis-tools-installed/650/2 diff --git a/fixtures/dump.sql b/fixtures/dump.sql index 28350fcd..e071c7e1 100644 --- a/fixtures/dump.sql +++ b/fixtures/dump.sql @@ -82,6 +82,45 @@ SET default_tablespace = ''; SET default_with_oids = false; +-- +-- Name: account_conversations; Type: TABLE; Schema: public; Owner: pinafore +-- + +CREATE TABLE public.account_conversations ( + id bigint NOT NULL, + account_id bigint, + conversation_id bigint, + participant_account_ids bigint[] DEFAULT '{}'::bigint[] NOT NULL, + status_ids bigint[] DEFAULT '{}'::bigint[] NOT NULL, + last_status_id bigint, + lock_version integer DEFAULT 0 NOT NULL, + unread boolean DEFAULT false NOT NULL +); + + +ALTER TABLE public.account_conversations OWNER TO pinafore; + +-- +-- Name: account_conversations_id_seq; Type: SEQUENCE; Schema: public; Owner: pinafore +-- + +CREATE SEQUENCE public.account_conversations_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER TABLE public.account_conversations_id_seq OWNER TO pinafore; + +-- +-- Name: account_conversations_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: pinafore +-- + +ALTER SEQUENCE public.account_conversations_id_seq OWNED BY public.account_conversations.id; + + -- -- Name: account_domain_blocks; Type: TABLE; Schema: public; Owner: pinafore -- @@ -558,7 +597,8 @@ CREATE TABLE public.domain_blocks ( created_at timestamp without time zone NOT NULL, updated_at timestamp without time zone NOT NULL, severity integer DEFAULT 0, - reject_media boolean DEFAULT false NOT NULL + reject_media boolean DEFAULT false NOT NULL, + reject_reports boolean DEFAULT false NOT NULL ); @@ -976,7 +1016,8 @@ CREATE TABLE public.mentions ( status_id bigint, created_at timestamp without time zone NOT NULL, updated_at timestamp without time zone NOT NULL, - account_id bigint + account_id bigint, + silent boolean DEFAULT false NOT NULL ); @@ -1202,6 +1243,43 @@ ALTER TABLE public.oauth_applications_id_seq OWNER TO pinafore; ALTER SEQUENCE public.oauth_applications_id_seq OWNED BY public.oauth_applications.id; +-- +-- Name: pghero_space_stats; Type: TABLE; Schema: public; Owner: pinafore +-- + +CREATE TABLE public.pghero_space_stats ( + id bigint NOT NULL, + database text, + schema text, + relation text, + size bigint, + captured_at timestamp without time zone +); + + +ALTER TABLE public.pghero_space_stats OWNER TO pinafore; + +-- +-- Name: pghero_space_stats_id_seq; Type: SEQUENCE; Schema: public; Owner: pinafore +-- + +CREATE SEQUENCE public.pghero_space_stats_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER TABLE public.pghero_space_stats_id_seq OWNER TO pinafore; + +-- +-- Name: pghero_space_stats_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: pinafore +-- + +ALTER SEQUENCE public.pghero_space_stats_id_seq OWNED BY public.pghero_space_stats.id; + + -- -- Name: preview_cards; Type: TABLE; Schema: public; Owner: pinafore -- @@ -1890,6 +1968,13 @@ ALTER TABLE public.web_settings_id_seq OWNER TO pinafore; ALTER SEQUENCE public.web_settings_id_seq OWNED BY public.web_settings.id; +-- +-- Name: account_conversations id; Type: DEFAULT; Schema: public; Owner: pinafore +-- + +ALTER TABLE ONLY public.account_conversations ALTER COLUMN id SET DEFAULT nextval('public.account_conversations_id_seq'::regclass); + + -- -- Name: account_domain_blocks id; Type: DEFAULT; Schema: public; Owner: pinafore -- @@ -2086,6 +2171,13 @@ ALTER TABLE ONLY public.oauth_access_tokens ALTER COLUMN id SET DEFAULT nextval( ALTER TABLE ONLY public.oauth_applications ALTER COLUMN id SET DEFAULT nextval('public.oauth_applications_id_seq'::regclass); +-- +-- Name: pghero_space_stats id; Type: DEFAULT; Schema: public; Owner: pinafore +-- + +ALTER TABLE ONLY public.pghero_space_stats ALTER COLUMN id SET DEFAULT nextval('public.pghero_space_stats_id_seq'::regclass); + + -- -- Name: preview_cards id; Type: DEFAULT; Schema: public; Owner: pinafore -- @@ -2191,6 +2283,14 @@ ALTER TABLE ONLY public.web_push_subscriptions ALTER COLUMN id SET DEFAULT nextv ALTER TABLE ONLY public.web_settings ALTER COLUMN id SET DEFAULT nextval('public.web_settings_id_seq'::regclass); +-- +-- Data for Name: account_conversations; Type: TABLE DATA; Schema: public; Owner: pinafore +-- + +COPY public.account_conversations (id, account_id, conversation_id, participant_account_ids, status_ids, last_status_id, lock_version, unread) FROM stdin; +\. + + -- -- Data for Name: account_domain_blocks; Type: TABLE DATA; Schema: public; Owner: pinafore -- @@ -2304,7 +2404,7 @@ COPY public.custom_filters (id, account_id, expires_at, phrase, context, irrever -- Data for Name: domain_blocks; Type: TABLE DATA; Schema: public; Owner: pinafore -- -COPY public.domain_blocks (id, domain, created_at, updated_at, severity, reject_media) FROM stdin; +COPY public.domain_blocks (id, domain, created_at, updated_at, severity, reject_media, reject_reports) FROM stdin; \. @@ -2397,7 +2497,7 @@ COPY public.media_attachments (id, status_id, file_file_name, file_content_type, -- Data for Name: mentions; Type: TABLE DATA; Schema: public; Owner: pinafore -- -COPY public.mentions (id, status_id, created_at, updated_at, account_id) FROM stdin; +COPY public.mentions (id, status_id, created_at, updated_at, account_id, silent) FROM stdin; \. @@ -2455,6 +2555,14 @@ COPY public.oauth_applications (id, name, uid, secret, redirect_uri, scopes, cre \. +-- +-- Data for Name: pghero_space_stats; Type: TABLE DATA; Schema: public; Owner: pinafore +-- + +COPY public.pghero_space_stats (id, database, schema, relation, size, captured_at) FROM stdin; +\. + + -- -- Data for Name: preview_cards; Type: TABLE DATA; Schema: public; Owner: pinafore -- @@ -2678,6 +2786,13 @@ COPY public.schema_migrations (version) FROM stdin; 20180813113448 20180814171349 20180820232245 +20180929222014 +20181007025445 +20181010141500 +20181017170937 +20181018205649 +20181024224956 +20181026034033 \. @@ -2805,6 +2920,13 @@ COPY public.web_settings (id, data, created_at, updated_at, user_id) FROM stdin; \. +-- +-- Name: account_conversations_id_seq; Type: SEQUENCE SET; Schema: public; Owner: pinafore +-- + +SELECT pg_catalog.setval('public.account_conversations_id_seq', 1, false); + + -- -- Name: account_domain_blocks_id_seq; Type: SEQUENCE SET; Schema: public; Owner: pinafore -- @@ -3001,6 +3123,13 @@ SELECT pg_catalog.setval('public.oauth_access_tokens_id_seq', 9, true); SELECT pg_catalog.setval('public.oauth_applications_id_seq', 1, true); +-- +-- Name: pghero_space_stats_id_seq; Type: SEQUENCE SET; Schema: public; Owner: pinafore +-- + +SELECT pg_catalog.setval('public.pghero_space_stats_id_seq', 1, false); + + -- -- Name: preview_cards_id_seq; Type: SEQUENCE SET; Schema: public; Owner: pinafore -- @@ -3113,6 +3242,14 @@ SELECT pg_catalog.setval('public.web_push_subscriptions_id_seq', 1, false); SELECT pg_catalog.setval('public.web_settings_id_seq', 4, true); +-- +-- Name: account_conversations account_conversations_pkey; Type: CONSTRAINT; Schema: public; Owner: pinafore +-- + +ALTER TABLE ONLY public.account_conversations + ADD CONSTRAINT account_conversations_pkey PRIMARY KEY (id); + + -- -- Name: account_domain_blocks account_domain_blocks_pkey; Type: CONSTRAINT; Schema: public; Owner: pinafore -- @@ -3345,6 +3482,14 @@ ALTER TABLE ONLY public.oauth_applications ADD CONSTRAINT oauth_applications_pkey PRIMARY KEY (id); +-- +-- Name: pghero_space_stats pghero_space_stats_pkey; Type: CONSTRAINT; Schema: public; Owner: pinafore +-- + +ALTER TABLE ONLY public.pghero_space_stats + ADD CONSTRAINT pghero_space_stats_pkey PRIMARY KEY (id); + + -- -- Name: preview_cards preview_cards_pkey; Type: CONSTRAINT; Schema: public; Owner: pinafore -- @@ -3495,6 +3640,20 @@ CREATE UNIQUE INDEX account_activity ON public.notifications USING btree (accoun CREATE INDEX hashtag_search_index ON public.tags USING btree (lower((name)::text) text_pattern_ops); +-- +-- Name: index_account_conversations_on_account_id; Type: INDEX; Schema: public; Owner: pinafore +-- + +CREATE INDEX index_account_conversations_on_account_id ON public.account_conversations USING btree (account_id); + + +-- +-- Name: index_account_conversations_on_conversation_id; Type: INDEX; Schema: public; Owner: pinafore +-- + +CREATE INDEX index_account_conversations_on_conversation_id ON public.account_conversations USING btree (conversation_id); + + -- -- Name: index_account_domain_blocks_on_account_id_and_domain; Type: INDEX; Schema: public; Owner: pinafore -- @@ -3845,6 +4004,13 @@ CREATE INDEX index_oauth_applications_on_owner_id_and_owner_type ON public.oauth CREATE UNIQUE INDEX index_oauth_applications_on_uid ON public.oauth_applications USING btree (uid); +-- +-- Name: index_pghero_space_stats_on_database_and_captured_at; Type: INDEX; Schema: public; Owner: pinafore +-- + +CREATE INDEX index_pghero_space_stats_on_database_and_captured_at ON public.pghero_space_stats USING btree (database, captured_at); + + -- -- Name: index_preview_cards_on_url; Type: INDEX; Schema: public; Owner: pinafore -- @@ -4013,6 +4179,13 @@ CREATE UNIQUE INDEX index_subscriptions_on_account_id_and_callback_url ON public CREATE UNIQUE INDEX index_tags_on_name ON public.tags USING btree (name); +-- +-- Name: index_unique_conversations; Type: INDEX; Schema: public; Owner: pinafore +-- + +CREATE UNIQUE INDEX index_unique_conversations ON public.account_conversations USING btree (account_id, conversation_id, participant_account_ids); + + -- -- Name: index_users_on_account_id; Type: INDEX; Schema: public; Owner: pinafore -- @@ -4357,6 +4530,14 @@ ALTER TABLE ONLY public.backups ADD CONSTRAINT fk_rails_096669d221 FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE SET NULL; +-- +-- Name: account_conversations fk_rails_1491654f9f; Type: FK CONSTRAINT; Schema: public; Owner: pinafore +-- + +ALTER TABLE ONLY public.account_conversations + ADD CONSTRAINT fk_rails_1491654f9f FOREIGN KEY (conversation_id) REFERENCES public.conversations(id) ON DELETE CASCADE; + + -- -- Name: accounts fk_rails_2320833084; Type: FK CONSTRAINT; Schema: public; Owner: pinafore -- @@ -4453,6 +4634,14 @@ ALTER TABLE ONLY public.status_pins ADD CONSTRAINT fk_rails_65c05552f1 FOREIGN KEY (status_id) REFERENCES public.statuses(id) ON DELETE CASCADE; +-- +-- Name: account_conversations fk_rails_6f5278b6e9; Type: FK CONSTRAINT; Schema: public; Owner: pinafore +-- + +ALTER TABLE ONLY public.account_conversations + ADD CONSTRAINT fk_rails_6f5278b6e9 FOREIGN KEY (account_id) REFERENCES public.accounts(id) ON DELETE CASCADE; + + -- -- Name: web_push_subscriptions fk_rails_751a9f390b; Type: FK CONSTRAINT; Schema: public; Owner: pinafore -- diff --git a/fixtures/system.tgz b/fixtures/system.tgz index 14a7abc0..e40e5dba 100644 Binary files a/fixtures/system.tgz and b/fixtures/system.tgz differ diff --git a/routes/_utils/arrays.js b/routes/_utils/arrays.js index 7de1c4e1..b13882a2 100644 --- a/routes/_utils/arrays.js +++ b/routes/_utils/arrays.js @@ -44,3 +44,12 @@ export function concat () { } return res } + +export function indexWhere (arr, cb) { + for (let i = 0; i < arr.length; i++) { + if (cb(arr[i], i)) { + return i + } + } + return -1 +} diff --git a/tests/fixtures.js b/tests/fixtures.js index a22a0a95..bcd0f7e1 100644 --- a/tests/fixtures.js +++ b/tests/fixtures.js @@ -4,10 +4,8 @@ export const homeTimeline = [ { content: 'pinned toot 1' }, { content: 'notification of unlisted message' }, { content: 'notification of followers-only message' }, - { content: 'notification of direct message' }, { content: 'this is unlisted' }, { content: 'this is followers-only' }, - { content: 'direct' }, { spoiler: 'kitten CW' }, { content: 'secret video' }, { content: "here's a video" }, diff --git a/tests/spec/003-basic-timeline-spec.js b/tests/spec/003-basic-timeline-spec.js index 2c3ec0d2..183c03e3 100644 --- a/tests/spec/003-basic-timeline-spec.js +++ b/tests/spec/003-basic-timeline-spec.js @@ -22,7 +22,7 @@ test('Shows the home timeline', async t => { await validateTimeline(t, homeTimeline) - await t.expect(getFirstVisibleStatus().getAttribute('aria-setsize')).eql('49') + await t.expect(getFirstVisibleStatus().getAttribute('aria-setsize')).eql('47') }) test('Shows notifications', async t => { diff --git a/tests/spec/005-status-types.js b/tests/spec/005-status-types.js index e1b09e55..2c4eedc5 100644 --- a/tests/spec/005-status-types.js +++ b/tests/spec/005-status-types.js @@ -5,7 +5,7 @@ import { Selector as $ } from 'testcafe' fixture`005-status-types.js` .page`http://localhost:4002` -test('shows direct vs followers-only vs regular', async t => { +test('shows followers-only vs regular in home timeline', async t => { await loginAsFoobar(t) await t .expect(getNthStatus(1).getAttribute('aria-label')).eql('Status by admin') @@ -18,11 +18,6 @@ test('shows direct vs followers-only vs regular', async t => { .expect($(`${getNthStatusSelector(2)} .status-toolbar button:nth-child(2)`).getAttribute('aria-label')) .eql('Cannot be boosted because this is followers-only') .expect($(`${getNthStatusSelector(2)} .status-toolbar button:nth-child(2)`).hasAttribute('disabled')).ok() - .expect(getNthStatus(3).getAttribute('aria-label')).eql('Direct message by admin') - .expect($(`${getNthStatusSelector(3)} .status-content`).innerText).contains('notification of direct message') - .expect($(`${getNthStatusSelector(3)} .status-toolbar button:nth-child(2)`).getAttribute('aria-label')) - .eql('Cannot be boosted because this is a direct message') - .expect($(`${getNthStatusSelector(3)} .status-toolbar button:nth-child(2)`).hasAttribute('disabled')).ok() }) test('shows direct vs followers-only vs regular in notifications', async t => { diff --git a/tests/spec/008-status-media.js b/tests/spec/008-status-media.js index facbafd0..c8c275e3 100644 --- a/tests/spec/008-status-media.js +++ b/tests/spec/008-status-media.js @@ -1,34 +1,44 @@ import { closeDialogButton, getNthStatus, getNthStatusSelector, modalDialogContents, scrollToStatus } from '../utils' import { loginAsFoobar } from '../roles' import { Selector as $ } from 'testcafe' +import { homeTimeline } from '../fixtures' +import { indexWhere } from '../../routes/_utils/arrays' fixture`008-status-media.js` .page`http://localhost:4002` test('shows sensitive images and videos', async t => { await loginAsFoobar(t) - await scrollToStatus(t, 7) - await t.expect($(`${getNthStatusSelector(7)} .status-media img`).exists).notOk() - .click($(`${getNthStatusSelector(7)} .status-sensitive-media-button`)) - .expect($(`${getNthStatusSelector(7)} .status-media img`).getAttribute('alt')).eql('kitten') - .expect($(`${getNthStatusSelector(7)} .status-media img`).hasAttribute('src')).ok() - .hover(getNthStatus(8)) - .expect($(`${getNthStatusSelector(8)} .status-media .play-video-button`).exists).notOk() - .click($(`${getNthStatusSelector(8)} .status-sensitive-media-button`)) - .expect($(`${getNthStatusSelector(8)} .status-media .play-video-button`).exists).ok() + + let kittenIdx = indexWhere(homeTimeline, _ => _.spoiler === 'kitten CW') + let videoIdx = indexWhere(homeTimeline, _ => _.content === 'secret video') + + await scrollToStatus(t, kittenIdx) + await t.expect($(`${getNthStatusSelector(kittenIdx)} .status-media img`).exists).notOk() + .click($(`${getNthStatusSelector(kittenIdx)} .status-sensitive-media-button`)) + .expect($(`${getNthStatusSelector(kittenIdx)} .status-media img`).getAttribute('alt')).eql('kitten') + .expect($(`${getNthStatusSelector(kittenIdx)} .status-media img`).hasAttribute('src')).ok() + .hover(getNthStatus(videoIdx)) + .expect($(`${getNthStatusSelector(videoIdx)} .status-media .play-video-button`).exists).notOk() + .click($(`${getNthStatusSelector(videoIdx)} .status-sensitive-media-button`)) + .expect($(`${getNthStatusSelector(videoIdx)} .status-media .play-video-button`).exists).ok() }) test('click and close image and video modals', async t => { await loginAsFoobar(t) - await scrollToStatus(t, 9) + + let videoIdx = indexWhere(homeTimeline, _ => _.content === "here's a video") + let kittenIdx = indexWhere(homeTimeline, _ => _.content === "here's an animated kitten gif") + + await scrollToStatus(t, videoIdx) await t.expect(modalDialogContents.exists).notOk() - .click($(`${getNthStatusSelector(9)} .play-video-button`)) + .click($(`${getNthStatusSelector(videoIdx)} .play-video-button`)) .expect(modalDialogContents.exists).ok() .click(closeDialogButton) .expect(modalDialogContents.exists).notOk() - .hover(getNthStatus(11)) - .hover(getNthStatus(12)) - .click($(`${getNthStatusSelector(12)} .show-image-button`)) + .hover(getNthStatus(kittenIdx - 1)) + .hover(getNthStatus(kittenIdx)) + .click($(`${getNthStatusSelector(kittenIdx)} .show-image-button`)) .expect(modalDialogContents.exists).ok() .click(closeDialogButton) .expect(modalDialogContents.exists).notOk() diff --git a/tests/spec/010-focus.js b/tests/spec/010-focus.js index 204e628d..840dc437 100644 --- a/tests/spec/010-focus.js +++ b/tests/spec/010-focus.js @@ -5,21 +5,26 @@ import { } from '../utils' import { loginAsFoobar } from '../roles' import { Selector as $ } from 'testcafe' +import { indexWhere } from '../../routes/_utils/arrays' +import { homeTimeline } from '../fixtures' fixture`010-focus.js` .page`http://localhost:4002` test('modal preserves focus', async t => { await loginAsFoobar(t) - await scrollToStatus(t, 9) + + let idx = indexWhere(homeTimeline, _ => _.content === "here's a video") + + await scrollToStatus(t, idx) // explicitly hover-focus-click - await t.hover($(`${getNthStatusSelector(9)} .play-video-button`)) - await focus(`${getNthStatusSelector(9)} .play-video-button`)() - await t.click($(`${getNthStatusSelector(9)} .play-video-button`)) + await t.hover($(`${getNthStatusSelector(idx)} .play-video-button`)) + await focus(`${getNthStatusSelector(idx)} .play-video-button`)() + await t.click($(`${getNthStatusSelector(idx)} .play-video-button`)) .click(closeDialogButton) .expect(modalDialogContents.exists).notOk() .expect(getActiveElementClass()).contains('play-video-button') - .expect(getActiveElementInsideNthStatus()).eql('9') + .expect(getActiveElementInsideNthStatus()).eql(idx.toString()) }) test('timeline preserves focus', async t => { diff --git a/tests/spec/017-compose-reply.js b/tests/spec/017-compose-reply.js index 29f269c2..1b5fab40 100644 --- a/tests/spec/017-compose-reply.js +++ b/tests/spec/017-compose-reply.js @@ -6,6 +6,8 @@ import { getNthStatus, getUrl, homeNavButton, notificationsNavButton, scrollToStatus } from '../utils' import { loginAsFoobar } from '../roles' +import { homeTimeline } from '../fixtures' +import { indexWhere } from '../../routes/_utils/arrays' fixture`017-compose-reply.js` .page`http://localhost:4002` @@ -52,48 +54,46 @@ test('replies have same privacy as replied-to status by default', async t => { .expect(getNthPostPrivacyButton(2).getAttribute('aria-label')).eql('Adjust privacy (currently Followers-only)') .click(getNthReplyButton(2)) .hover(getNthStatus(3)) - .click(getNthReplyButton(3)) - .expect(getNthPostPrivacyButton(3).getAttribute('aria-label')).eql('Adjust privacy (currently Direct)') - .click(getNthReplyButton(3)) .hover(getNthStatus(4)) .hover(getNthStatus(5)) - .hover(getNthStatus(6)) - .hover(getNthStatus(7)) - .click(getNthReplyButton(7)) - .expect(getNthPostPrivacyButton(7).getAttribute('aria-label')).eql('Adjust privacy (currently Public)') - .click(getNthReplyButton(7)) + .click(getNthReplyButton(5)) + .expect(getNthPostPrivacyButton(5).getAttribute('aria-label')).eql('Adjust privacy (currently Public)') + .click(getNthReplyButton(5)) }) test('replies have same CW as replied-to status', async t => { await loginAsFoobar(t) - await scrollToStatus(t, 7) - await t.click(getNthReplyButton(7)) - .expect(getNthReplyContentWarningInput(7).value).eql('kitten CW') - .click(getNthStatus(7)) + let kittenIdx = indexWhere(homeTimeline, _ => _.spoiler === 'kitten CW') + await scrollToStatus(t, kittenIdx) + await t.click(getNthReplyButton(kittenIdx)) + .expect(getNthReplyContentWarningInput(kittenIdx).value).eql('kitten CW') + .click(getNthStatus(kittenIdx)) .click(getNthReplyButton(0)) .expect(getNthReplyContentWarningInput(0).value).eql('kitten CW') }) test('replies save deletions of CW', async t => { await loginAsFoobar(t) - await scrollToStatus(t, 7) - await t.click(getNthReplyButton(7)) - .expect(getNthReplyContentWarningInput(7).value).eql('kitten CW') - .click(getNthReplyContentWarningButton(7)) - .expect(getNthReplyContentWarningInput(7).exists).notOk() - .click(getNthStatus(7)) + let kittenIdx = indexWhere(homeTimeline, _ => _.spoiler === 'kitten CW') + await scrollToStatus(t, kittenIdx) + await t.click(getNthReplyButton(kittenIdx)) + .expect(getNthReplyContentWarningInput(kittenIdx).value).eql('kitten CW') + .click(getNthReplyContentWarningButton(kittenIdx)) + .expect(getNthReplyContentWarningInput(kittenIdx).exists).notOk() + .click(getNthStatus(kittenIdx)) .click(getNthReplyButton(0)) .expect(getNthReplyContentWarningInput(0).exists).notOk() }) test('replies save changes to CW', async t => { await loginAsFoobar(t) - await scrollToStatus(t, 7) - await t.click(getNthReplyButton(7)) - .expect(getNthReplyContentWarningInput(7).value).eql('kitten CW') - .typeText(getNthReplyContentWarningInput(7), ' yolo', { paste: true }) - .expect(getNthReplyContentWarningInput(7).value).eql('kitten CW yolo') - .click(getNthStatus(7)) + let kittenIdx = indexWhere(homeTimeline, _ => _.spoiler === 'kitten CW') + await scrollToStatus(t, kittenIdx) + await t.click(getNthReplyButton(kittenIdx)) + .expect(getNthReplyContentWarningInput(kittenIdx).value).eql('kitten CW') + .typeText(getNthReplyContentWarningInput(kittenIdx), ' yolo', { paste: true }) + .expect(getNthReplyContentWarningInput(kittenIdx).value).eql('kitten CW yolo') + .click(getNthStatus(kittenIdx)) .click(getNthReplyButton(0)) .expect(getNthReplyContentWarningInput(0).value).eql('kitten CW yolo') }) diff --git a/tests/spec/100-favorite-unfavorite.js b/tests/spec/100-favorite-unfavorite.js index 6c1aad9f..39e0b6a4 100644 --- a/tests/spec/100-favorite-unfavorite.js +++ b/tests/spec/100-favorite-unfavorite.js @@ -4,6 +4,8 @@ import { scrollToBottomOfTimeline, scrollToTopOfTimeline } from '../utils' import { loginAsFoobar } from '../roles' +import { indexWhere } from '../../routes/_utils/arrays' +import { homeTimeline } from '../fixtures' fixture`100-favorite-unfavorite.js` .page`http://localhost:4002` @@ -59,20 +61,21 @@ test('unfavorites a status', async t => { test('Keeps the correct favorites count', async t => { await loginAsFoobar(t) + let idx = indexWhere(homeTimeline, _ => _.content === 'this is unlisted') await t - .hover(getNthStatus(4)) - .click(getNthFavoriteButton(4)) - .expect(getNthFavorited(4)).eql('true') - .click(getNthStatus(4)) + .hover(getNthStatus(idx)) + .click(getNthFavoriteButton(idx)) + .expect(getNthFavorited(idx)).eql('true') + .click(getNthStatus(idx)) .expect(getUrl()).contains('/status') .expect(getNthFavorited(0)).eql('true') .expect(getFavoritesCount()).eql(2) .click(homeNavButton) .expect(getUrl()).eql('http://localhost:4002/') - .hover(getNthStatus(4)) - .click(getNthFavoriteButton(4)) - .expect(getNthFavorited(4)).eql('false') - .click(getNthStatus(4)) + .hover(getNthStatus(idx)) + .click(getNthFavoriteButton(idx)) + .expect(getNthFavorited(idx)).eql('false') + .click(getNthStatus(idx)) .expect(getUrl()).contains('/status') .expect(getNthFavorited(0)).eql('false') .expect(getFavoritesCount()).eql(1) diff --git a/tests/spec/101-reblog-unreblog.js b/tests/spec/101-reblog-unreblog.js index 008ac50b..9e08ba0a 100644 --- a/tests/spec/101-reblog-unreblog.js +++ b/tests/spec/101-reblog-unreblog.js @@ -37,44 +37,44 @@ test('reblogs a status', async t => { test('unreblogs a status', async t => { await loginAsFoobar(t) await t - .hover(getNthStatus(4)) - .expect(getNthReblogged(4)).eql('false') - .click(getNthReblogButton(4)) - .expect(getNthReblogged(4)).eql('true') - .click(getNthReblogButton(4)) - .expect(getNthReblogged(4)).eql('false') + .hover(getNthStatus(3)) + .expect(getNthReblogged(3)).eql('false') + .click(getNthReblogButton(3)) + .expect(getNthReblogged(3)).eql('true') + .click(getNthReblogButton(3)) + .expect(getNthReblogged(3)).eql('false') // scroll down and back up to force an unrender await scrollToBottomOfTimeline(t) await scrollToTopOfTimeline(t) await t - .hover(getNthStatus(4)) - .expect(getNthReblogged(4)).eql('false') + .hover(getNthStatus(3)) + .expect(getNthReblogged(3)).eql('false') .click(notificationsNavButton) .click(homeNavButton) - .expect(getNthReblogged(4)).eql('false') + .expect(getNthReblogged(3)).eql('false') .click(notificationsNavButton) .navigateTo('/') - .expect(getNthReblogged(4)).eql('false') - .click(getNthReblogButton(4)) - .expect(getNthReblogged(4)).eql('true') + .expect(getNthReblogged(3)).eql('false') + .click(getNthReblogButton(3)) + .expect(getNthReblogged(3)).eql('true') }) test('Keeps the correct reblogs count', async t => { await loginAsFoobar(t) await t - .hover(getNthStatus(4)) - .expect(getNthReblogged(4)).eql('true') - .click(getNthStatus(4)) + .hover(getNthStatus(3)) + .expect(getNthReblogged(3)).eql('true') + .click(getNthStatus(3)) .expect(getUrl()).contains('/status') .expect(getNthReblogged(0)).eql('true') .expect(getReblogsCount()).eql(2) .click(homeNavButton) .expect(getUrl()).eql('http://localhost:4002/') - .hover(getNthStatus(4)) - .click(getNthReblogButton(4)) - .expect(getNthReblogged(4)).eql('false') - .click(getNthStatus(4)) + .hover(getNthStatus(3)) + .click(getNthReblogButton(3)) + .expect(getNthReblogged(3)).eql('false') + .click(getNthStatus(3)) .expect(getUrl()).contains('/status') .expect(getNthReblogged(0)).eql('false') .expect(getReblogsCount()).eql(1) diff --git a/tests/spec/102-notifications.js b/tests/spec/102-notifications.js index 4cae0106..29211359 100644 --- a/tests/spec/102-notifications.js +++ b/tests/spec/102-notifications.js @@ -1,37 +1,25 @@ import { loginAsFoobar } from '../roles' import { - getNthStatus, getNthStatusSelector, getUrl, homeNavButton, notificationsNavButton, - validateTimeline + getNthStatus, getUrl, homeNavButton, notificationsNavButton } from '../utils' -import { favoriteStatusAs } from '../serverActions' -import { notifications } from '../fixtures' -import { Selector as $ } from 'testcafe' +import { favoriteStatusAs, postAs } from '../serverActions' fixture`102-notifications.js` .page`http://localhost:4002` test('shows unread notifications', async t => { + let { id } = await postAs('foobar', 'somebody please favorite this to validate me') await loginAsFoobar(t) await t - .hover(getNthStatus(0)) - .hover(getNthStatus(2)) - .hover(getNthStatus(4)) - .hover(getNthStatus(5)) .expect(notificationsNavButton.getAttribute('aria-label')).eql('Notifications') - let statusId = (await $(`${getNthStatusSelector(5)} .status-relative-date`).getAttribute('href')) - .split('/').slice(-1)[0] - await favoriteStatusAs('admin', statusId) + await favoriteStatusAs('admin', id) await t .expect(notificationsNavButton.getAttribute('aria-label')).eql('Notifications (1)') .click(notificationsNavButton) .expect(getUrl()).contains('/notifications') .expect(notificationsNavButton.getAttribute('aria-label')).eql('Notifications (current page)') - await validateTimeline(t, [ - { - favoritedBy: 'admin', - content: 'this is followers-only' - } - ].concat(notifications)) + .expect(getNthStatus(0).innerText).contains('somebody please favorite this to validate me') + .expect(getNthStatus(0).innerText).match(/admin\s+favorited your status/) await t .click(homeNavButton) .expect(notificationsNavButton.getAttribute('aria-label')).eql('Notifications')