parent
211fdd52f5
commit
d4ed442a7e
@ -1,47 +0,0 @@
|
||||
import { Record as ImmutableRecord } from 'immutable';
|
||||
|
||||
import { normalizePoll } from '../poll';
|
||||
|
||||
describe('normalizePoll()', () => {
|
||||
it('adds base fields', () => {
|
||||
const poll = { 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);
|
||||
});
|
||||
|
||||
it('normalizes a Pleroma logged-out poll', () => {
|
||||
const { poll } = require('soapbox/__fixtures__/pleroma-status-with-poll.json');
|
||||
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 } = require('soapbox/__fixtures__/pleroma-status-with-poll-with-emojis.json');
|
||||
const result = normalizePoll(poll);
|
||||
|
||||
// Emojifies poll options
|
||||
expect(result.options.get(1)?.title_emojified)
|
||||
.toContain('emojione');
|
||||
|
||||
// Parses emojis as Immutable.Record's
|
||||
expect(ImmutableRecord.isRecord(result.emojis.get(0))).toBe(true);
|
||||
expect(result.emojis.get(1)?.shortcode).toEqual('soapbox');
|
||||
});
|
||||
});
|
@ -1,102 +0,0 @@
|
||||
/**
|
||||
* 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,
|
||||
fromJS,
|
||||
} from 'immutable';
|
||||
|
||||
import emojify from 'soapbox/features/emoji';
|
||||
import { normalizeEmoji } from 'soapbox/normalizers/emoji';
|
||||
import { makeEmojiMap } from 'soapbox/utils/normalizers';
|
||||
|
||||
import type { Emoji, PollOption } from 'soapbox/types/entities';
|
||||
|
||||
// https://docs.joinmastodon.org/entities/poll/
|
||||
export const PollRecord = ImmutableRecord({
|
||||
emojis: ImmutableList<Emoji>(),
|
||||
expired: false,
|
||||
expires_at: '',
|
||||
id: '',
|
||||
multiple: false,
|
||||
options: ImmutableList<PollOption>(),
|
||||
voters_count: 0,
|
||||
votes_count: 0,
|
||||
own_votes: null as ImmutableList<number> | null,
|
||||
voted: false,
|
||||
pleroma: ImmutableMap<string, any>(),
|
||||
});
|
||||
|
||||
// Sub-entity of Poll
|
||||
export const PollOptionRecord = ImmutableRecord({
|
||||
title: '',
|
||||
votes_count: 0,
|
||||
|
||||
// Internal fields
|
||||
title_emojified: '',
|
||||
});
|
||||
|
||||
// Normalize emojis
|
||||
const normalizeEmojis = (entity: ImmutableMap<string, any>) => {
|
||||
return entity.update('emojis', ImmutableList(), emojis => {
|
||||
return emojis.map(normalizeEmoji);
|
||||
});
|
||||
};
|
||||
|
||||
const normalizePollOption = (option: ImmutableMap<string, any> | string, emojis: ImmutableList<ImmutableMap<string, string>> = ImmutableList()) => {
|
||||
const emojiMap = makeEmojiMap(emojis);
|
||||
|
||||
if (typeof option === 'string') {
|
||||
const titleEmojified = emojify(escapeTextContentForBrowser(option), emojiMap);
|
||||
|
||||
return PollOptionRecord({
|
||||
title: option,
|
||||
title_emojified: titleEmojified,
|
||||
});
|
||||
}
|
||||
|
||||
const titleEmojified = emojify(escapeTextContentForBrowser(option.get('title')), emojiMap);
|
||||
|
||||
return PollOptionRecord(
|
||||
option.set('title_emojified', titleEmojified),
|
||||
);
|
||||
};
|
||||
|
||||
// Normalize poll options
|
||||
const normalizePollOptions = (poll: ImmutableMap<string, any>) => {
|
||||
const emojis = poll.get('emojis');
|
||||
|
||||
return poll.update('options', (options: ImmutableList<ImmutableMap<string, any>>) => {
|
||||
return options.map(option => normalizePollOption(option, emojis));
|
||||
});
|
||||
};
|
||||
|
||||
// Normalize own_votes to `null` if empty (like Mastodon)
|
||||
const normalizePollOwnVotes = (poll: ImmutableMap<string, any>) => {
|
||||
return poll.update('own_votes', ownVotes => {
|
||||
return ownVotes?.size > 0 ? ownVotes : null;
|
||||
});
|
||||
};
|
||||
|
||||
// Whether the user voted in the poll
|
||||
const normalizePollVoted = (poll: ImmutableMap<string, any>) => {
|
||||
return poll.update('voted', voted => {
|
||||
return typeof voted === 'boolean' ? voted : poll.get('own_votes')?.size > 0;
|
||||
});
|
||||
};
|
||||
|
||||
export const normalizePoll = (poll: Record<string, any>) => {
|
||||
return PollRecord(
|
||||
ImmutableMap(fromJS(poll)).withMutations((poll: ImmutableMap<string, any>) => {
|
||||
normalizeEmojis(poll);
|
||||
normalizePollOptions(poll);
|
||||
normalizePollOwnVotes(poll);
|
||||
normalizePollVoted(poll);
|
||||
}),
|
||||
);
|
||||
};
|
@ -0,0 +1,44 @@
|
||||
import { pollSchema } from '../poll';
|
||||
|
||||
describe('normalizePoll()', () => {
|
||||
it('adds base fields', () => {
|
||||
const poll = { id: '1', options: [{ title: 'Apples' }, { title: 'Oranges' }] };
|
||||
const result = pollSchema.parse(poll);
|
||||
|
||||
const expected = {
|
||||
options: [
|
||||
{ title: 'Apples', votes_count: 0 },
|
||||
{ title: 'Oranges', votes_count: 0 },
|
||||
],
|
||||
emojis: [],
|
||||
expired: false,
|
||||
multiple: false,
|
||||
voters_count: 0,
|
||||
votes_count: 0,
|
||||
own_votes: null,
|
||||
voted: false,
|
||||
};
|
||||
|
||||
expect(result).toMatchObject(expected);
|
||||
});
|
||||
|
||||
it('normalizes a Pleroma logged-out poll', () => {
|
||||
const { poll } = require('soapbox/__fixtures__/pleroma-status-with-poll.json');
|
||||
const result = pollSchema.parse(poll);
|
||||
|
||||
// Adds logged-in fields
|
||||
expect(result.voted).toBe(false);
|
||||
expect(result.own_votes).toBe(null);
|
||||
});
|
||||
|
||||
it('normalizes poll with emojis', () => {
|
||||
const { poll } = require('soapbox/__fixtures__/pleroma-status-with-poll-with-emojis.json');
|
||||
const result = pollSchema.parse(poll);
|
||||
|
||||
// Emojifies poll options
|
||||
expect(result.options[1]?.title_emojified)
|
||||
.toContain('emojione');
|
||||
|
||||
expect(result.emojis[1]?.shortcode).toEqual('soapbox');
|
||||
});
|
||||
});
|
@ -0,0 +1,50 @@
|
||||
import escapeTextContentForBrowser from 'escape-html';
|
||||
import { z } from 'zod';
|
||||
|
||||
import emojify from 'soapbox/features/emoji';
|
||||
|
||||
import { customEmojiSchema } from './custom-emoji';
|
||||
import { filteredArray, makeCustomEmojiMap } from './utils';
|
||||
|
||||
const pollOptionSchema = z.object({
|
||||
title: z.string().catch(''),
|
||||
votes_count: z.number().catch(0),
|
||||
});
|
||||
|
||||
const pollSchema = z.object({
|
||||
emojis: filteredArray(customEmojiSchema),
|
||||
expired: z.boolean().catch(false),
|
||||
expires_at: z.string().datetime().catch(new Date().toUTCString()),
|
||||
id: z.string(),
|
||||
multiple: z.boolean().catch(false),
|
||||
options: z.array(pollOptionSchema).min(2),
|
||||
voters_count: z.number().catch(0),
|
||||
votes_count: z.number().catch(0),
|
||||
own_votes: z.array(z.number()).nonempty().nullable().catch(null),
|
||||
voted: z.boolean().catch(false),
|
||||
pleroma: z.object({
|
||||
non_anonymous: z.boolean().catch(false),
|
||||
}).optional().catch(undefined),
|
||||
}).transform((poll) => {
|
||||
const emojiMap = makeCustomEmojiMap(poll.emojis);
|
||||
|
||||
const emojifiedOptions = poll.options.map((option) => ({
|
||||
...option,
|
||||
title_emojified: emojify(escapeTextContentForBrowser(option.title), emojiMap),
|
||||
}));
|
||||
|
||||
// If the user has votes, they have certainly voted.
|
||||
if (poll.own_votes?.length) {
|
||||
poll.voted = true;
|
||||
}
|
||||
|
||||
return {
|
||||
...poll,
|
||||
options: emojifiedOptions,
|
||||
};
|
||||
});
|
||||
|
||||
type Poll = z.infer<typeof pollSchema>;
|
||||
type PollOption = Poll['options'][number];
|
||||
|
||||
export { pollSchema, type Poll, type PollOption };
|
Loading…
Reference in new issue