Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { beforeAll, describe, expect, it } from 'vitest';
import type { Feed } from '../src';
import {
createTestClient,
createTestTokenGenerator,
getTestUser,
} from './utils';

describe('skip_activity_current_feed', () => {
const user1 = getTestUser();
const user2 = getTestUser();
const client1 = createTestClient();
const client2 = createTestClient();
let feed1: Feed;
let feed2: Feed;

beforeAll(async () => {
await client1.connectUser(user1, createTestTokenGenerator(user1));
await client2.connectUser(user2, createTestTokenGenerator(user2));
feed1 = client1.feed('timeline', user1.id);
feed2 = client2.feed('timeline', user2.id);
await feed1.getOrCreate();
await feed2.getOrCreate();
await feed1.follow(feed2.feed);
await feed2.addActivity({
type: 'post',
text: 'Hello, world!',
});
});

it('should not create a feed for the activity current feed if read with skip_activity_current_feed', async () => {
await feed1.getOrCreate({
enrichment_options: { skip_activity_current_feed: true },
});

expect(feed1.currentState.activities?.length).toBe(1);
expect(client1['activeFeeds'][feed2.feed]).toBeUndefined();
});

it('should create a feed for the activity current feed if read without skip_activity_current_feed', async () => {
await feed1.getOrCreate();

expect(feed1.currentState.activities?.length).toBe(1);
expect(client1['activeFeeds'][feed2.feed]).toBeDefined();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -70,17 +70,24 @@ describe(handleActivityAdded.name, () => {
const existing = generateActivityResponse();
feed.state.partialNext({ activities: [existing] });
const event = generateActivityAddedEvent();
const newActivitiesAddedSpy = vi.spyOn(feed, 'newActivitiesAdded' as any);
handleActivityAdded.call(feed, event);

const stateAfter = feed.currentState;
expect(stateAfter.activities).toHaveLength(2);
expect(stateAfter.activities?.[0]).toBe(existing);
expect(stateAfter.activities?.[1]).toBe(event.activity);
expect(newActivitiesAddedSpy).toHaveBeenCalledWith([event.activity], {
fromWebSocket: true,
});

vi.resetAllMocks();
});

it('does not duplicate if activity already exists', () => {
const existing = generateActivityResponse();
feed.state.partialNext({ activities: [existing] });
const newActivitiesAddedSpy = vi.spyOn(feed, 'newActivitiesAdded' as any);

const event = generateActivityAddedEvent({
activity: { id: existing.id },
Expand All @@ -93,6 +100,9 @@ describe(handleActivityAdded.name, () => {
expect(stateAfter).toBe(stateBefore);
expect(stateAfter.activities).toHaveLength(1);
expect(stateAfter.activities?.[0]).toBe(existing);
expect(newActivitiesAddedSpy).not.toHaveBeenCalled();

vi.resetAllMocks();
});

it(`onActivityAdded filters out activity if it returns false`, () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export function addActivitiesToState(
newActivities: ActivityResponse[],
activities: ActivityResponse[] | undefined,
position: 'start' | 'end',
{ fromWebSocket }: { fromWebSocket: boolean } = { fromWebSocket: false },
) {
if (activities === undefined) {
return {
Expand All @@ -33,7 +34,7 @@ export function addActivitiesToState(
...activities,
...(position === 'end' ? newActivitiesDeduplicated : []),
];
this.newActivitiesAdded(newActivitiesDeduplicated);
this.newActivitiesAdded(newActivitiesDeduplicated, { fromWebSocket });

result = { changed: true, activities: updatedActivities };
}
Expand All @@ -55,6 +56,7 @@ export function handleActivityAdded(
[event.activity],
currentActivities,
this.currentState.addNewActivitiesTo,
{ fromWebSocket: true },
);
if (result.changed) {
const activity = event.activity;
Expand Down
110 changes: 102 additions & 8 deletions packages/feeds-client/src/feed/feed.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,14 +128,6 @@ describe(`getOrCreate`, () => {
user_score: 0.8,
},
watch: true,
activity_selector_options: {
sort: [
{
field: 'created_at',
direction: 'desc',
},
],
},
interest_weights: {
technology: 0.8,
travel: -1,
Expand Down Expand Up @@ -219,3 +211,105 @@ describe(`getOrCreate`, () => {
).rejects.toThrow('Only one getOrCreate call is allowed at a time');
});
});

describe(`newActivitiesAdded`, () => {
let feed: Feed;
let client: Record<keyof FeedsClient | 'getOrCreateActiveFeed', Mock>;

beforeEach(() => {
client = {
getOrCreateActiveFeed: vi.fn(),
hydrateCapabilitiesCache: vi.fn(),
hydratePollCache: vi.fn(),
} as unknown as Record<keyof FeedsClient | 'getOrCreateActiveFeed', Mock>;
const feedResponse = generateFeedResponse({
id: 'user-123',
group_id: 'user',
});
feed = new Feed(
client as unknown as FeedsClient,
feedResponse.group_id,
feedResponse.id,
feedResponse,
);
});

it('should not create feeds if enrichment options are set to skip_all', () => {
feed.state.partialNext({
last_get_or_create_request_config: {
enrichment_options: {
skip_all: true,
},
},
});

feed['newActivitiesAdded']([generateActivityResponse()]);

expect(client['getOrCreateActiveFeed']).not.toHaveBeenCalled();
});

it('should not create feeds if enrichment options are set to skip_activity_current_feed', () => {
feed.state.partialNext({
last_get_or_create_request_config: {
enrichment_options: {
skip_activity_current_feed: true,
},
},
});

feed['newActivitiesAdded']([generateActivityResponse()]);

expect(client['getOrCreateActiveFeed']).not.toHaveBeenCalled();
});

it('should deduplicate feeds from acitivties', () => {
const feed1 = generateFeedResponse({
group_id: 'user',
id: '123',
feed: 'user:123',
});
const feed2 = generateFeedResponse({
group_id: 'user',
id: '456',
feed: 'user:456',
});
const activity1 = generateActivityResponse({ current_feed: feed1 });
const activity2 = generateActivityResponse({ current_feed: feed2 });
const activity3 = generateActivityResponse({ current_feed: feed1 });

feed['newActivitiesAdded']([activity1, activity2, activity3]);

expect(client['getOrCreateActiveFeed']).toHaveBeenCalledTimes(2);
expect(client['getOrCreateActiveFeed']).toHaveBeenCalledWith({
group: feed1.group_id,
id: feed1.id,
data: feed1,
fromWebSocket: false,
});
expect(client['getOrCreateActiveFeed']).toHaveBeenCalledWith({
group: feed2.group_id,
id: feed2.id,
data: feed2,
fromWebSocket: false,
});
});

it(`should set fromWebSocket flag to true if activities are added from a WebSocket event`, () => {
const currentFeed = generateFeedResponse({
group_id: 'user',
id: '123',
feed: 'user:123',
});
feed['newActivitiesAdded'](
[generateActivityResponse({ current_feed: currentFeed })],
{ fromWebSocket: true },
);

expect(client['getOrCreateActiveFeed']).toHaveBeenCalledWith({
group: currentFeed.group_id,
id: currentFeed.id,
data: currentFeed,
fromWebSocket: true,
});
});
});
65 changes: 49 additions & 16 deletions packages/feeds-client/src/feed/feed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import {
checkHasAnotherPage,
Constants,
feedsLoggerSystem,
ownFeedFields,
uniqueArrayMerge,
} from '../utils';
import { handleActivityFeedback } from './event-handlers/activity/handle-activity-feedback';
Expand Down Expand Up @@ -953,12 +954,13 @@ export class Feed extends FeedApi {
event.activity.current_feed &&
currentActivity?.current_feed
) {
event.activity.current_feed.own_capabilities =
currentActivity.current_feed.own_capabilities;
event.activity.current_feed.own_follows =
currentActivity.current_feed.own_follows;
event.activity.current_feed.own_membership =
currentActivity.current_feed.own_membership;
ownFeedFields.forEach((field) => {
if (field in currentActivity.current_feed!) {
// @ts-expect-error TODO: fix this
event.activity.current_feed![field] =
currentActivity.current_feed![field];
}
});
}
}
// @ts-expect-error intersection of handler arguments results to never
Expand All @@ -974,17 +976,48 @@ export class Feed extends FeedApi {
this.eventDispatcher.dispatch(event);
}

protected newActivitiesAdded(activities: ActivityResponse[]) {
protected newActivitiesAdded(
activities: ActivityResponse[],
options: {
fromWebSocket: boolean;
} = { fromWebSocket: false },
) {
this.client.hydratePollCache(activities);
this.getOrCreateFeeds(activities, options);
}

activities.forEach((activity) => {
if (activity.current_feed) {
getOrCreateActiveFeed.bind(this.client)(
activity.current_feed.group_id,
activity.current_feed.id,
activity.current_feed,
);
}
});
private getOrCreateFeeds(
activities: ActivityResponse[],
options: {
fromWebSocket: boolean;
},
) {
const enrichmentOptions =
this.currentState.last_get_or_create_request_config?.enrichment_options;
if (
!enrichmentOptions?.skip_activity_current_feed &&
!enrichmentOptions?.skip_all
) {
const feedsToGetOrCreate = new Map<string, FeedResponse>();
activities.forEach((activity) => {
if (
activity.current_feed &&
!feedsToGetOrCreate.has(activity.current_feed.feed)
) {
feedsToGetOrCreate.set(
activity.current_feed.feed,
activity.current_feed,
);
}
});
feedsToGetOrCreate.values().forEach((feed) => {
getOrCreateActiveFeed.bind(this.client)({
group: feed.group_id,
id: feed.id,
data: feed,
fromWebSocket: options.fromWebSocket,
});
});
}
}
}
Loading