Remove normalizeAd

This commit is contained in:
Alex Gleason 2023-05-02 18:30:21 -05:00
parent 81de7c268e
commit 54d8d12054
No known key found for this signature in database
GPG Key ID: 7211D1F99744FBB7
16 changed files with 76 additions and 77 deletions

View File

@ -74,7 +74,7 @@ const importFetchedGroup = (group: APIEntity) =>
importFetchedGroups([group]);
const importFetchedGroups = (groups: APIEntity[]) => {
const entities = filteredArray(groupSchema).catch([]).parse(groups);
const entities = filteredArray(groupSchema).parse(groups);
return importGroups(entities);
};

View File

@ -2,6 +2,7 @@ import React from 'react';
import { buildGroup, buildGroupRelationship } from 'soapbox/jest/factory';
import { render, screen } from 'soapbox/jest/test-helpers';
import { GroupRoles } from 'soapbox/schemas/group-member';
import { Group } from 'soapbox/types/entities';
import GroupActionButton from '../group-action-button';
@ -45,7 +46,7 @@ describe('<GroupActionButton />', () => {
beforeEach(() => {
group = buildGroup({
relationship: buildGroupRelationship({
member: null,
member: false,
}),
});
});
@ -98,7 +99,7 @@ describe('<GroupActionButton />', () => {
relationship: buildGroupRelationship({
requested: false,
member: true,
role: 'owner',
role: GroupRoles.OWNER,
}),
});
});
@ -116,7 +117,7 @@ describe('<GroupActionButton />', () => {
relationship: buildGroupRelationship({
requested: false,
member: true,
role: 'user',
role: GroupRoles.USER,
}),
});
});

View File

@ -17,7 +17,7 @@ describe('<GroupOptionsButton />', () => {
requested: false,
member: true,
blocked_by: true,
role: 'user',
role: GroupRoles.USER,
}),
});
});

View File

@ -1,9 +1,13 @@
import { v4 as uuidv4 } from 'uuid';
import {
adSchema,
cardSchema,
groupSchema,
groupRelationshipSchema,
groupTagSchema,
type Ad,
type Card,
type Group,
type GroupRelationship,
type GroupTag,
@ -12,22 +16,34 @@ import {
// TODO: there's probably a better way to create these factory functions.
// This looks promising but didn't work on my first attempt: https://github.com/anatine/zod-plugins/tree/main/packages/zod-mock
function buildGroup(props: Record<string, any> = {}): Group {
function buildCard(props: Partial<Card> = {}): Card {
return cardSchema.parse(Object.assign({
url: 'https://soapbox.test',
}, props));
}
function buildGroup(props: Partial<Group> = {}): Group {
return groupSchema.parse(Object.assign({
id: uuidv4(),
}, props));
}
function buildGroupRelationship(props: Record<string, any> = {}): GroupRelationship {
function buildGroupRelationship(props: Partial<GroupRelationship> = {}): GroupRelationship {
return groupRelationshipSchema.parse(Object.assign({
id: uuidv4(),
}, props));
}
function buildGroupTag(props: Record<string, any> = {}): GroupTag {
function buildGroupTag(props: Partial<GroupTag> = {}): GroupTag {
return groupTagSchema.parse(Object.assign({
id: uuidv4(),
}, props));
}
export { buildGroup, buildGroupRelationship, buildGroupTag };
function buildAd(props: Partial<Ad> = {}): Ad {
return adSchema.parse(Object.assign({
card: buildCard(),
}, props));
}
export { buildCard, buildGroup, buildGroupRelationship, buildGroupTag, buildAd };

View File

@ -26,5 +26,4 @@ export { StatusRecord, normalizeStatus } from './status';
export { StatusEditRecord, normalizeStatusEdit } from './status-edit';
export { TagRecord, normalizeTag } from './tag';
export { AdRecord, normalizeAd } from './soapbox/ad';
export { SoapboxConfigRecord, normalizeSoapboxConfig } from './soapbox/soapbox-config';

View File

@ -1,28 +0,0 @@
import {
Map as ImmutableMap,
Record as ImmutableRecord,
fromJS,
} from 'immutable';
import { normalizeCard } from '../card';
import type { Ad } from 'soapbox/features/ads/providers';
export const AdRecord = ImmutableRecord<Ad>({
card: normalizeCard({}),
impression: undefined as string | undefined,
expires_at: undefined as string | undefined,
reason: undefined as string | undefined,
});
/** Normalizes an ad from Soapbox Config. */
export const normalizeAd = (ad: Record<string, any>) => {
const map = ImmutableMap<string, any>(fromJS(ad));
const card = normalizeCard(map.get('card').toJS());
const expiresAt = map.get('expires_at') || map.get('expires');
return AdRecord(map.merge({
card,
expires_at: expiresAt,
}));
};

View File

@ -6,12 +6,12 @@ import {
} from 'immutable';
import trimStart from 'lodash/trimStart';
import { adSchema } from 'soapbox/schemas';
import { filteredArray } from 'soapbox/schemas/utils';
import { normalizeUsername } from 'soapbox/utils/input';
import { toTailwind } from 'soapbox/utils/tailwind';
import { generateAccent } from 'soapbox/utils/theme';
import { normalizeAd } from './ad';
import type {
Ad,
PromoPanelItem,
@ -125,8 +125,12 @@ export const SoapboxConfigRecord = ImmutableRecord({
type SoapboxConfigMap = ImmutableMap<string, any>;
const normalizeAds = (soapboxConfig: SoapboxConfigMap): SoapboxConfigMap => {
const ads = ImmutableList<Record<string, any>>(soapboxConfig.get('ads'));
return soapboxConfig.set('ads', ads.map(normalizeAd));
if (soapboxConfig.has('ads')) {
const ads = filteredArray(adSchema).parse(soapboxConfig.get('ads').toJS());
return soapboxConfig.set('ads', ads);
} else {
return soapboxConfig;
}
};
const normalizeCryptoAddress = (address: unknown): CryptoAddress => {

View File

@ -2,7 +2,8 @@ import { useQuery } from '@tanstack/react-query';
import { Ad, getProvider } from 'soapbox/features/ads/providers';
import { useAppDispatch } from 'soapbox/hooks';
import { normalizeAd } from 'soapbox/normalizers';
import { adSchema } from 'soapbox/schemas';
import { filteredArray } from 'soapbox/schemas/utils';
import { isExpired } from 'soapbox/utils/ads';
const AdKeys = {
@ -28,7 +29,9 @@ function useAds() {
});
// Filter out expired ads.
const data = result.data?.map(normalizeAd).filter(ad => !isExpired(ad));
const data = filteredArray(adSchema)
.parse(result.data)
.filter(ad => !isExpired(ad));
return {
...result,

View File

@ -22,7 +22,7 @@ const accountSchema = z.object({
created_at: z.string().datetime().catch(new Date().toUTCString()),
discoverable: z.boolean().catch(false),
display_name: z.string().catch(''),
emojis: filteredArray(customEmojiSchema).catch([]),
emojis: filteredArray(customEmojiSchema),
favicon: z.string().catch(''),
fields: z.any(), // TODO
followers_count: z.number().catch(0),

View File

@ -19,7 +19,7 @@ const groupSchema = z.object({
deleted_at: z.string().datetime().or(z.null()).catch(null),
display_name: z.string().catch(''),
domain: z.string().catch(''),
emojis: filteredArray(customEmojiSchema).catch([]),
emojis: filteredArray(customEmojiSchema),
group_visibility: z.string().catch(''), // TruthSocial
header: z.string().catch(headerMissing),
header_static: z.string().catch(''),

View File

@ -1,22 +1,11 @@
/**
* Schemas
*/
export { accountSchema } from './account';
export { customEmojiSchema } from './custom-emoji';
export { groupSchema } from './group';
export { groupMemberSchema } from './group-member';
export { groupRelationshipSchema } from './group-relationship';
export { groupTagSchema } from './group-tag';
export { relationshipSchema } from './relationship';
export { accountSchema, type Account } from './account';
export { cardSchema, type Card } from './card';
export { customEmojiSchema, type CustomEmoji } from './custom-emoji';
export { groupSchema, type Group } from './group';
export { groupMemberSchema, type GroupMember } from './group-member';
export { groupRelationshipSchema, type GroupRelationship } from './group-relationship';
export { groupTagSchema, type GroupTag } from './group-tag';
export { relationshipSchema, type Relationship } from './relationship';
/**
* Entity Types
*/
export type { Account } from './account';
export type { Card } from './card';
export type { CustomEmoji } from './custom-emoji';
export type { Group } from './group';
export type { GroupMember } from './group-member';
export type { GroupRelationship } from './group-relationship';
export type { GroupTag } from './group-tag';
export type { Relationship } from './relationship';
// Soapbox
export { adSchema, type Ad } from './soapbox/ad';

View File

@ -0,0 +1,14 @@
import { z } from 'zod';
import { cardSchema } from '../card';
const adSchema = z.object({
card: cardSchema,
impression: z.string().optional().catch(undefined),
expires_at: z.string().optional().catch(undefined),
reason: z.string().optional().catch(undefined),
});
type Ad = z.infer<typeof adSchema>;
export { adSchema, type Ad };

View File

@ -4,7 +4,7 @@ import type { CustomEmoji } from './custom-emoji';
/** Validates individual items in an array, dropping any that aren't valid. */
function filteredArray<T extends z.ZodTypeAny>(schema: T) {
return z.any().array()
return z.any().array().catch([])
.transform((arr) => (
arr.map((item) => {
const parsed = schema.safeParse(item);

View File

@ -1,4 +1,3 @@
import { AdRecord } from 'soapbox/normalizers/soapbox/ad';
import {
PromoPanelItemRecord,
FooterItemRecord,
@ -8,7 +7,6 @@ import {
type Me = string | null | false | undefined;
type Ad = ReturnType<typeof AdRecord>;
type PromoPanelItem = ReturnType<typeof PromoPanelItemRecord>;
type FooterItem = ReturnType<typeof FooterItemRecord>;
type CryptoAddress = ReturnType<typeof CryptoAddressRecord>;
@ -16,9 +14,12 @@ type SoapboxConfig = ReturnType<typeof SoapboxConfigRecord>;
export {
Me,
Ad,
PromoPanelItem,
FooterItem,
CryptoAddress,
SoapboxConfig,
};
export type {
Ad,
} from 'soapbox/schemas';

View File

@ -1,4 +1,4 @@
import { normalizeAd } from 'soapbox/normalizers';
import { buildAd } from 'soapbox/jest/factory';
import { isExpired } from '../ads';
@ -14,10 +14,10 @@ test('isExpired()', () => {
const epoch = now.getTime();
// Sanity tests.
expect(isExpired(normalizeAd({ expires_at: iso }))).toBe(true);
expect(isExpired(normalizeAd({ expires_at: new Date(epoch + 999999).toISOString() }))).toBe(false);
expect(isExpired(buildAd({ expires_at: iso }))).toBe(true);
expect(isExpired(buildAd({ expires_at: new Date(epoch + 999999).toISOString() }))).toBe(false);
// Testing the 5-minute mark.
expect(isExpired(normalizeAd({ expires_at: new Date(epoch + threeMins).toISOString() }), fiveMins)).toBe(true);
expect(isExpired(normalizeAd({ expires_at: new Date(epoch + fiveMins + 1000).toISOString() }), fiveMins)).toBe(false);
expect(isExpired(buildAd({ expires_at: new Date(epoch + threeMins).toISOString() }), fiveMins)).toBe(true);
expect(isExpired(buildAd({ expires_at: new Date(epoch + fiveMins + 1000).toISOString() }), fiveMins)).toBe(false);
});

View File

@ -1,4 +1,4 @@
import type { Ad } from 'soapbox/types/soapbox';
import type { Ad } from 'soapbox/schemas';
/** Time (ms) window to not display an ad if it's about to expire. */
const AD_EXPIRY_THRESHOLD = 5 * 60 * 1000;