From 6812e7bfd4ad27f701105d77793aeeb599d01645 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 12 Mar 2022 15:22:18 -0600 Subject: [PATCH] Move Poll normalizer into its own module --- .../normalizers/__tests__/poll-test.js | 48 ++++++++++ app/soapbox/normalizers/poll.ts | 88 +++++++++++++++++++ app/soapbox/normalizers/status.ts | 72 +-------------- 3 files changed, 138 insertions(+), 70 deletions(-) create mode 100644 app/soapbox/normalizers/__tests__/poll-test.js create mode 100644 app/soapbox/normalizers/poll.ts diff --git a/app/soapbox/normalizers/__tests__/poll-test.js b/app/soapbox/normalizers/__tests__/poll-test.js new file mode 100644 index 000000000..4cc9dbbbc --- /dev/null +++ b/app/soapbox/normalizers/__tests__/poll-test.js @@ -0,0 +1,48 @@ +import { Record as ImmutableRecord, fromJS } from 'immutable'; + +import { normalizePoll } from '../poll'; + +describe('normalizePoll()', () => { + it('adds base fields', () => { + const poll = fromJS({ options: [{ title: 'Apples' }] }); + const result = normalizePoll(poll); + + const expected = { + options: [{ title: 'Apples', votes_count: 0 }], + emojis: [], + expired: false, + multiple: false, + voters_count: 0, + votes_count: 0, + own_votes: null, + voted: false, + }; + + expect(ImmutableRecord.isRecord(result)).toBe(true); + expect(ImmutableRecord.isRecord(result.options.get(0))).toBe(true); + expect(result.toJS()).toMatchObject(expected); + expect(result.expires_at instanceof Date).toBe(true); + }); + + it('normalizes a Pleroma logged-out poll', () => { + const poll = fromJS(require('soapbox/__fixtures__/pleroma-status-with-poll.json')).get('poll'); + const result = normalizePoll(poll); + + // Adds logged-in fields + expect(result.voted).toBe(false); + expect(result.own_votes).toBe(null); + }); + + it('normalizes poll with emojis', () => { + const poll = fromJS(require('soapbox/__fixtures__/pleroma-status-with-poll-with-emojis.json')).get('poll'); + const result = normalizePoll(poll); + + // Emojifies poll options + expect(result.options.get(1).title_emojified) + .toEqual('Custom emoji :gleason_excited: '); + + // Parses emojis as Immutable.Record's + expect(ImmutableRecord.isRecord(result.emojis.get(0))).toBe(true); + expect(result.emojis.get(1).shortcode).toEqual('soapbox'); + }); +}); diff --git a/app/soapbox/normalizers/poll.ts b/app/soapbox/normalizers/poll.ts new file mode 100644 index 000000000..fa127702e --- /dev/null +++ b/app/soapbox/normalizers/poll.ts @@ -0,0 +1,88 @@ +/** + * Poll normalizer: + * Converts API polls into our internal format. + * @see {@link https://docs.joinmastodon.org/entities/poll/} + */ +import escapeTextContentForBrowser from 'escape-html'; +import { + Map as ImmutableMap, + List as ImmutableList, + Record as ImmutableRecord, +} from 'immutable'; + +import emojify from 'soapbox/features/emoji/emoji'; +import { normalizeEmoji } from 'soapbox/normalizers/emoji'; +import { makeEmojiMap } from 'soapbox/utils/normalizers'; + +// https://docs.joinmastodon.org/entities/poll/ +const PollRecord = ImmutableRecord({ + emojis: ImmutableList(), + expired: false, + expires_at: new Date(), + id: '', + multiple: false, + options: ImmutableList(), + voters_count: 0, + votes_count: 0, + own_votes: null, + voted: false, +}); + +// Sub-entity of Poll +const PollOptionRecord = ImmutableRecord({ + title: '', + votes_count: 0, + + // Internal fields + title_emojified: '', +}); + +// Normalize emojis +const normalizeEmojis = (entity: ImmutableMap) => { + return entity.update('emojis', ImmutableList(), emojis => { + return emojis.map(normalizeEmoji); + }); +}; + +const normalizePollOption = (option: ImmutableMap, emojis: ImmutableList> = ImmutableList()) => { + const emojiMap = makeEmojiMap(emojis); + const titleEmojified = emojify(escapeTextContentForBrowser(option.get('title')), emojiMap); + + return PollOptionRecord( + option.set('title_emojified', titleEmojified), + ); +}; + +// Normalize poll options +const normalizePollOptions = (poll: ImmutableMap) => { + const emojis = poll.get('emojis'); + + return poll.update('options', (options: ImmutableList>) => { + return options.map(option => normalizePollOption(option, emojis)); + }); +}; + +// Normalize own_votes to `null` if empty (like Mastodon) +const normalizePollOwnVotes = (poll: ImmutableMap) => { + return poll.update('own_votes', ownVotes => { + return ownVotes?.size > 0 ? ownVotes : null; + }); +}; + +// Whether the user voted in the poll +const normalizePollVoted = (poll: ImmutableMap) => { + return poll.update('voted', voted => { + return typeof voted === 'boolean' ? voted : poll.get('own_votes')?.size > 0; + }); +}; + +export const normalizePoll = (poll: ImmutableMap) => { + return PollRecord( + poll.withMutations((poll: ImmutableMap) => { + normalizeEmojis(poll); + normalizePollOptions(poll); + normalizePollOwnVotes(poll); + normalizePollVoted(poll); + }), + ); +}; diff --git a/app/soapbox/normalizers/status.ts b/app/soapbox/normalizers/status.ts index a6d10aa73..91c52cfa3 100644 --- a/app/soapbox/normalizers/status.ts +++ b/app/soapbox/normalizers/status.ts @@ -3,18 +3,17 @@ * Converts API statuses into our internal format. * @see {@link https://docs.joinmastodon.org/entities/status/} */ -import escapeTextContentForBrowser from 'escape-html'; import { Map as ImmutableMap, List as ImmutableList, Record as ImmutableRecord, } from 'immutable'; -import emojify from 'soapbox/features/emoji/emoji'; import { normalizeEmoji } from 'soapbox/normalizers/emoji'; import { normalizeMention } from 'soapbox/normalizers/mention'; +import { normalizePoll } from 'soapbox/normalizers/poll'; import { IStatus } from 'soapbox/types'; -import { mergeDefined, makeEmojiMap } from 'soapbox/utils/normalizers'; +import { mergeDefined } from 'soapbox/utils/normalizers'; // https://docs.joinmastodon.org/entities/status/ const StatusRecord = ImmutableRecord({ @@ -73,29 +72,6 @@ const AttachmentRecord = ImmutableRecord({ status: null, }); -// https://docs.joinmastodon.org/entities/poll/ -const PollRecord = ImmutableRecord({ - emojis: ImmutableList(), - expired: false, - expires_at: new Date(), - id: '', - multiple: false, - options: ImmutableList(), - voters_count: 0, - votes_count: 0, - own_votes: null, - voted: false, -}); - -// Sub-entity of Poll -const PollOptionRecord = ImmutableRecord({ - title: '', - votes_count: 0, - - // Internal fields - title_emojified: '', -}); - // Ensure attachments have required fields const normalizeAttachment = (attachment: ImmutableMap) => { const url = [ @@ -131,50 +107,6 @@ const normalizeEmojis = (entity: ImmutableMap) => { }); }; -const normalizePollOption = (option: ImmutableMap, emojis: ImmutableList> = ImmutableList()) => { - const emojiMap = makeEmojiMap(emojis); - const titleEmojified = emojify(escapeTextContentForBrowser(option.get('title')), emojiMap); - - return PollOptionRecord( - option.set('title_emojified', titleEmojified), - ); -}; - -// Normalize poll options -const normalizePollOptions = (poll: ImmutableMap) => { - const emojis = poll.get('emojis'); - - return poll.update('options', (options: ImmutableList>) => { - return options.map(option => normalizePollOption(option, emojis)); - }); -}; - -// Normalize own_votes to `null` if empty (like Mastodon) -const normalizePollOwnVotes = (poll: ImmutableMap) => { - return poll.update('own_votes', ownVotes => { - return ownVotes?.size > 0 ? ownVotes : null; - }); -}; - -// Whether the user voted in the poll -const normalizePollVoted = (poll: ImmutableMap) => { - return poll.update('voted', voted => { - return typeof voted === 'boolean' ? voted : poll.get('own_votes')?.size > 0; - }); -}; - -// Normalize the actual poll -const normalizePoll = (poll: ImmutableMap) => { - return PollRecord( - poll.withMutations((poll: ImmutableMap) => { - normalizeEmojis(poll); - normalizePollOptions(poll); - normalizePollOwnVotes(poll); - normalizePollVoted(poll); - }), - ); -}; - // Normalize the poll in the status, if applicable const normalizeStatusPoll = (status: ImmutableMap) => { if (status.hasIn(['poll', 'options'])) {