diff --git a/app/soapbox/actions/__tests__/account-notes.test.ts b/app/soapbox/actions/__tests__/account-notes.test.ts
index 8b85eecc5..a00a9d877 100644
--- a/app/soapbox/actions/__tests__/account-notes.test.ts
+++ b/app/soapbox/actions/__tests__/account-notes.test.ts
@@ -1,10 +1,11 @@
import { Map as ImmutableMap } from 'immutable';
import { __stub } from 'soapbox/api';
+import { buildRelationship } from 'soapbox/jest/factory';
import { mockStore, rootState } from 'soapbox/jest/test-helpers';
import { ReducerRecord, EditRecord } from 'soapbox/reducers/account-notes';
-import { normalizeAccount, normalizeRelationship } from '../../normalizers';
+import { normalizeAccount } from '../../normalizers';
import { changeAccountNoteComment, initAccountNoteModal, submitAccountNote } from '../account-notes';
import type { Account } from 'soapbox/types/entities';
@@ -66,7 +67,7 @@ describe('initAccountNoteModal()', () => {
beforeEach(() => {
const state = rootState
- .set('relationships', ImmutableMap({ '1': normalizeRelationship({ note: 'hello' }) }));
+ .set('relationships', ImmutableMap({ '1': buildRelationship({ note: 'hello' }) }));
store = mockStore(state);
});
diff --git a/app/soapbox/actions/__tests__/accounts.test.ts b/app/soapbox/actions/__tests__/accounts.test.ts
index d9faa0213..c13f8ef90 100644
--- a/app/soapbox/actions/__tests__/accounts.test.ts
+++ b/app/soapbox/actions/__tests__/accounts.test.ts
@@ -1,10 +1,11 @@
import { Map as ImmutableMap } from 'immutable';
import { __stub } from 'soapbox/api';
+import { buildRelationship } from 'soapbox/jest/factory';
import { mockStore, rootState } from 'soapbox/jest/test-helpers';
import { ListRecord, ReducerRecord } from 'soapbox/reducers/user-lists';
-import { normalizeAccount, normalizeInstance, normalizeRelationship } from '../../normalizers';
+import { normalizeAccount, normalizeInstance } from '../../normalizers';
import {
authorizeFollowRequest,
blockAccount,
@@ -1340,7 +1341,7 @@ describe('fetchRelationships()', () => {
describe('without newAccountIds', () => {
beforeEach(() => {
const state = rootState
- .set('relationships', ImmutableMap({ [id]: normalizeRelationship({}) }))
+ .set('relationships', ImmutableMap({ [id]: buildRelationship() }))
.set('me', '123');
store = mockStore(state);
});
diff --git a/app/soapbox/features/ui/components/__tests__/subscribe-button.test.tsx b/app/soapbox/features/ui/components/__tests__/subscribe-button.test.tsx
index d0ec92f96..5edc9636b 100644
--- a/app/soapbox/features/ui/components/__tests__/subscribe-button.test.tsx
+++ b/app/soapbox/features/ui/components/__tests__/subscribe-button.test.tsx
@@ -1,8 +1,9 @@
-// import { Map as ImmutableMap } from 'immutable';
import React from 'react';
-import { render, screen } from '../../../../jest/test-helpers';
-import { normalizeAccount, normalizeRelationship } from '../../../../normalizers';
+import { buildRelationship } from 'soapbox/jest/factory';
+import { render, screen } from 'soapbox/jest/test-helpers';
+import { normalizeAccount } from 'soapbox/normalizers';
+
import SubscribeButton from '../subscription-button';
import type { ReducerAccount } from 'soapbox/reducers/accounts';
@@ -19,162 +20,10 @@ describe('', () => {
describe('with "accountNotifies" disabled', () => {
it('renders nothing', () => {
- const account = normalizeAccount({ ...justin, relationship: normalizeRelationship({ following: true }) }) as ReducerAccount;
+ const account = normalizeAccount({ ...justin, relationship: buildRelationship({ following: true }) }) as ReducerAccount;
render(, undefined, store);
expect(screen.queryAllByTestId('icon-button')).toHaveLength(0);
});
});
-
- // describe('with "accountNotifies" enabled', () => {
- // beforeEach(() => {
- // store = {
- // ...store,
- // instance: normalizeInstance({
- // version: '3.4.1 (compatible; TruthSocial 1.0.0)',
- // software: 'TRUTHSOCIAL',
- // pleroma: ImmutableMap({}),
- // }),
- // };
- // });
-
- // describe('when the relationship is requested', () => {
- // beforeEach(() => {
- // account = normalizeAccount({ ...account, relationship: normalizeRelationship({ requested: true }) });
-
- // store = {
- // ...store,
- // accounts: ImmutableMap({
- // '1': account,
- // }),
- // };
- // });
-
- // it('renders the button', () => {
- // render(, null, store);
- // expect(screen.getByTestId('icon-button')).toBeInTheDocument();
- // });
-
- // describe('when the user "isSubscribed"', () => {
- // beforeEach(() => {
- // account = normalizeAccount({
- // ...account,
- // relationship: normalizeRelationship({ requested: true, notifying: true }),
- // });
-
- // store = {
- // ...store,
- // accounts: ImmutableMap({
- // '1': account,
- // }),
- // };
- // });
-
- // it('renders the unsubscribe button', () => {
- // render(, null, store);
- // expect(screen.getByTestId('icon-button').title).toEqual(`Unsubscribe to notifications from @${account.acct}`);
- // });
- // });
-
- // describe('when the user is not "isSubscribed"', () => {
- // beforeEach(() => {
- // account = normalizeAccount({
- // ...account,
- // relationship: normalizeRelationship({ requested: true, notifying: false }),
- // });
-
- // store = {
- // ...store,
- // accounts: ImmutableMap({
- // '1': account,
- // }),
- // };
- // });
-
- // it('renders the unsubscribe button', () => {
- // render(, null, store);
- // expect(screen.getByTestId('icon-button').title).toEqual(`Subscribe to notifications from @${account.acct}`);
- // });
- // });
- // });
-
- // describe('when the user is not following the account', () => {
- // beforeEach(() => {
- // account = normalizeAccount({ ...account, relationship: normalizeRelationship({ following: false }) });
-
- // store = {
- // ...store,
- // accounts: ImmutableMap({
- // '1': account,
- // }),
- // };
- // });
-
- // it('renders nothing', () => {
- // render(, null, store);
- // expect(screen.queryAllByTestId('icon-button')).toHaveLength(0);
- // });
- // });
-
- // describe('when the user is following the account', () => {
- // beforeEach(() => {
- // account = normalizeAccount({ ...account, relationship: normalizeRelationship({ following: true }) });
-
- // store = {
- // ...store,
- // accounts: ImmutableMap({
- // '1': account,
- // }),
- // };
- // });
-
- // it('renders the button', () => {
- // render(, null, store);
- // expect(screen.getByTestId('icon-button')).toBeInTheDocument();
- // });
-
- // describe('when the user "isSubscribed"', () => {
- // beforeEach(() => {
- // account = normalizeAccount({
- // ...account,
- // relationship: normalizeRelationship({ requested: true, notifying: true }),
- // });
-
- // store = {
- // ...store,
- // accounts: ImmutableMap({
- // '1': account,
- // }),
- // };
- // });
-
- // it('renders the unsubscribe button', () => {
- // render(, null, store);
- // expect(screen.getByTestId('icon-button').title).toEqual(`Unsubscribe to notifications from @${account.acct}`);
- // });
- // });
-
- // describe('when the user is not "isSubscribed"', () => {
- // beforeEach(() => {
- // account = normalizeAccount({
- // ...account,
- // relationship: normalizeRelationship({ requested: true, notifying: false }),
- // });
-
- // store = {
- // ...store,
- // accounts: ImmutableMap({
- // '1': account,
- // }),
- // };
- // });
-
- // it('renders the unsubscribe button', () => {
- // render(, null, store);
- // expect(screen.getByTestId('icon-button').title).toEqual(`Subscribe to notifications from @${account.acct}`);
- // });
- // });
- // });
- // });
-
});
diff --git a/app/soapbox/jest/factory.ts b/app/soapbox/jest/factory.ts
index ca9f2b8f0..07f4bc7d2 100644
--- a/app/soapbox/jest/factory.ts
+++ b/app/soapbox/jest/factory.ts
@@ -6,11 +6,13 @@ import {
groupSchema,
groupRelationshipSchema,
groupTagSchema,
+ relationshipSchema,
type Ad,
type Card,
type Group,
type GroupRelationship,
type GroupTag,
+ type Relationship,
} from 'soapbox/schemas';
// TODO: there's probably a better way to create these factory functions.
@@ -46,4 +48,17 @@ function buildAd(props: Partial = {}): Ad {
}, props));
}
-export { buildCard, buildGroup, buildGroupRelationship, buildGroupTag, buildAd };
\ No newline at end of file
+function buildRelationship(props: Partial = {}): Relationship {
+ return relationshipSchema.parse(Object.assign({
+ id: uuidv4(),
+ }, props));
+}
+
+export {
+ buildCard,
+ buildGroup,
+ buildGroupRelationship,
+ buildGroupTag,
+ buildAd,
+ buildRelationship,
+};
\ No newline at end of file
diff --git a/app/soapbox/normalizers/index.ts b/app/soapbox/normalizers/index.ts
index ef7dbd7ca..d22bce0c9 100644
--- a/app/soapbox/normalizers/index.ts
+++ b/app/soapbox/normalizers/index.ts
@@ -20,7 +20,6 @@ export { LocationRecord, normalizeLocation } from './location';
export { MentionRecord, normalizeMention } from './mention';
export { NotificationRecord, normalizeNotification } from './notification';
export { PollRecord, PollOptionRecord, normalizePoll } from './poll';
-export { RelationshipRecord, normalizeRelationship } from './relationship';
export { StatusRecord, normalizeStatus } from './status';
export { StatusEditRecord, normalizeStatusEdit } from './status-edit';
export { TagRecord, normalizeTag } from './tag';
diff --git a/app/soapbox/normalizers/relationship.ts b/app/soapbox/normalizers/relationship.ts
deleted file mode 100644
index f492a00e9..000000000
--- a/app/soapbox/normalizers/relationship.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-/**
- * Relationship normalizer:
- * Converts API relationships into our internal format.
- * @see {@link https://docs.joinmastodon.org/entities/relationship/}
- */
-import {
- Map as ImmutableMap,
- Record as ImmutableRecord,
- fromJS,
-} from 'immutable';
-
-// https://docs.joinmastodon.org/entities/relationship/
-// https://api.pleroma.social/#operation/AccountController.relationships
-export const RelationshipRecord = ImmutableRecord({
- blocked_by: false,
- blocking: false,
- domain_blocking: false,
- endorsed: false,
- followed_by: false,
- following: false,
- id: '',
- muting: false,
- muting_notifications: false,
- note: '',
- notifying: false,
- requested: false,
- showing_reblogs: false,
- subscribing: false,
-});
-
-export const normalizeRelationship = (relationship: Record) => {
- return RelationshipRecord(
- ImmutableMap(fromJS(relationship)),
- );
-};
diff --git a/app/soapbox/queries/__tests__/chats.test.ts b/app/soapbox/queries/__tests__/chats.test.ts
index 65bb6294d..981250456 100644
--- a/app/soapbox/queries/__tests__/chats.test.ts
+++ b/app/soapbox/queries/__tests__/chats.test.ts
@@ -3,8 +3,9 @@ import sumBy from 'lodash/sumBy';
import { useEffect } from 'react';
import { __stub } from 'soapbox/api';
+import { buildRelationship } from 'soapbox/jest/factory';
import { createTestStore, mockStore, queryClient, renderHook, rootState, waitFor } from 'soapbox/jest/test-helpers';
-import { normalizeChatMessage, normalizeRelationship } from 'soapbox/normalizers';
+import { normalizeChatMessage } from 'soapbox/normalizers';
import { normalizeEmojiReaction } from 'soapbox/normalizers/emoji-reaction';
import { Store } from 'soapbox/store';
import { ChatMessage } from 'soapbox/types/entities';
@@ -120,7 +121,7 @@ describe('useChatMessages', () => {
const state = rootState
.set(
'relationships',
- ImmutableMap({ '1': normalizeRelationship({ blocked_by: true }) }),
+ ImmutableMap({ '1': buildRelationship({ blocked_by: true }) }),
);
store = mockStore(state);
});
@@ -239,7 +240,7 @@ describe('useChat()', () => {
mock.onGet(`/api/v1/pleroma/chats/${chat.id}`).reply(200, chat);
mock
.onGet(`/api/v1/accounts/relationships?id[]=${chat.account.id}`)
- .reply(200, [normalizeRelationship({ id: relationshipId, blocked_by: true })]);
+ .reply(200, [buildRelationship({ id: relationshipId, blocked_by: true })]);
});
});
diff --git a/app/soapbox/queries/__tests__/relationships.test.ts b/app/soapbox/queries/__tests__/relationships.test.ts
index 6466da7ff..02db36166 100644
--- a/app/soapbox/queries/__tests__/relationships.test.ts
+++ b/app/soapbox/queries/__tests__/relationships.test.ts
@@ -1,8 +1,8 @@
import { useEffect } from 'react';
import { __stub } from 'soapbox/api';
+import { buildRelationship } from 'soapbox/jest/factory';
import { createTestStore, queryClient, renderHook, rootState, waitFor } from 'soapbox/jest/test-helpers';
-import { normalizeRelationship } from 'soapbox/normalizers';
import { Store } from 'soapbox/store';
import { useFetchRelationships } from '../relationships';
@@ -25,7 +25,7 @@ describe('useFetchRelationships()', () => {
__stub((mock) => {
mock
.onGet(`/api/v1/accounts/relationships?id[]=${id}`)
- .reply(200, [normalizeRelationship({ id, blocked_by: true })]);
+ .reply(200, [buildRelationship({ id, blocked_by: true })]);
});
});
@@ -55,7 +55,7 @@ describe('useFetchRelationships()', () => {
__stub((mock) => {
mock
.onGet(`/api/v1/accounts/relationships?id[]=${ids[0]}&id[]=${ids[1]}`)
- .reply(200, ids.map((id) => normalizeRelationship({ id, blocked_by: true })));
+ .reply(200, ids.map((id) => buildRelationship({ id, blocked_by: true })));
});
});
diff --git a/app/soapbox/reducers/relationships.ts b/app/soapbox/reducers/relationships.ts
index 2eb035ec4..40d062f78 100644
--- a/app/soapbox/reducers/relationships.ts
+++ b/app/soapbox/reducers/relationships.ts
@@ -2,7 +2,7 @@ import { List as ImmutableList, Map as ImmutableMap } from 'immutable';
import get from 'lodash/get';
import { STREAMING_FOLLOW_RELATIONSHIPS_UPDATE } from 'soapbox/actions/streaming';
-import { normalizeRelationship } from 'soapbox/normalizers/relationship';
+import { type Relationship, relationshipSchema } from 'soapbox/schemas';
import { ACCOUNT_NOTE_SUBMIT_SUCCESS } from '../actions/account-notes';
import {
@@ -35,13 +35,16 @@ import {
import type { AnyAction } from 'redux';
import type { APIEntity } from 'soapbox/types/entities';
-type Relationship = ReturnType;
type State = ImmutableMap;
type APIEntities = Array;
const normalizeRelationships = (state: State, relationships: APIEntities) => {
relationships.forEach(relationship => {
- state = state.set(relationship.id, normalizeRelationship(relationship));
+ try {
+ state = state.set(relationship.id, relationshipSchema.parse(relationship));
+ } catch (_e) {
+ // do nothing
+ }
});
return state;
@@ -84,8 +87,12 @@ const followStateToRelationship = (followState: string) => {
};
const updateFollowRelationship = (state: State, id: string, followState: string) => {
- const map = followStateToRelationship(followState);
- return state.update(id, normalizeRelationship({}), relationship => relationship.merge(map));
+ const relationship = state.get(id) || relationshipSchema.parse({ id });
+
+ return state.set(id, {
+ ...relationship,
+ ...followStateToRelationship(followState),
+ });
};
export default function relationships(state: State = ImmutableMap(), action: AnyAction) {
diff --git a/app/soapbox/schemas/relationship.ts b/app/soapbox/schemas/relationship.ts
index 7d1e109c8..003cf747a 100644
--- a/app/soapbox/schemas/relationship.ts
+++ b/app/soapbox/schemas/relationship.ts
@@ -19,4 +19,4 @@ const relationshipSchema = z.object({
type Relationship = z.infer;
-export { relationshipSchema, Relationship };
\ No newline at end of file
+export { relationshipSchema, type Relationship };
\ No newline at end of file
diff --git a/app/soapbox/types/entities.ts b/app/soapbox/types/entities.ts
index ebbdd197d..27082f76f 100644
--- a/app/soapbox/types/entities.ts
+++ b/app/soapbox/types/entities.ts
@@ -21,7 +21,6 @@ import {
NotificationRecord,
PollRecord,
PollOptionRecord,
- RelationshipRecord,
StatusEditRecord,
StatusRecord,
TagRecord,
@@ -52,7 +51,6 @@ type Mention = ReturnType;
type Notification = ReturnType;
type Poll = ReturnType;
type PollOption = ReturnType;
-type Relationship = ReturnType;
type StatusEdit = ReturnType;
type Tag = ReturnType;
@@ -96,7 +94,6 @@ export {
Notification,
Poll,
PollOption,
- Relationship,
Status,
StatusEdit,
Tag,
@@ -111,4 +108,5 @@ export type {
Group,
GroupMember,
GroupRelationship,
+ Relationship,
} from 'soapbox/schemas';
\ No newline at end of file