docbrown/app/javascript/articles/__tests__/Feed.test.jsx
Ridhwana 8003691b37
Restructure the way we render the feed + add billboard locations on signed in feed (#19652)
* feat: fix of there is no article

* feat: set the feed order in a data structure rather than in a view

* feat: update yarn.lock

* feat: push the items to the array

* feat: update feed to only show billboards if we have the correct length of items in the feed

* fix: organizedFeedItems.legth

* feat: rename 'featured' to 'image'

* feat: rename 'featured' to 'image'

* feat: rename 'featured' to 'image'

* feat: add some utilities for the feed that we can use in the FeedTest

* feat/WIP: the setup for the feed tests and a first working test

* test: imageItem

* fix: the podcasts can be passed through as an array of objects in the feedItems so that they can be grouped in one card

* feat: remove podcastEpisode state and the attribute in the object

* fix: check that the items exist before trying to slice them

* feat: setup the userdata and the podcast data in the test

* whoops - commit the podcast episodes

* feat: write soem more tests for the feed and including the podcasts

* feat: add some more tests

* feat: add more billboards tests to chcek the order of stuff

* feat: set the timeframe not empty

* feat: update the logic for organizaed feed by inserting the last on first

* doc: jsdoc for functions

* refactor: break the code up into smaller functions

* refactor: make the code more readable and easier to follow

* refactor: pull function out into a utlity

* feat: add specs to utility

* feat: chcek if pinned post

* test the latest timeframe correctly

* chore: update var name

* move the podcast items out of the object clause

* feat: update text

* feat: add a pack file that duplicates initializeDisplayAdVisibility

* feat: create callbacks that will help us to determine when the feed has been rendered so that we can observe the dsplay ads accordingly

* chore: rename to billboards instead of display ad

* feat: first pass at some error handling whilst maintaining the order of the items

* test: for feed error

* feat: abstract out some code

* update the feed items for errors

* chore: comment

* feat: update the variable names

* feat: update honeybadger message
2023-07-03 15:00:44 +02:00

300 lines
11 KiB
JavaScript

/* eslint-disable no-irregular-whitespace */
import { h } from 'preact';
import { render, waitFor } from '@testing-library/preact';
import fetch from 'jest-fetch-mock';
import '@testing-library/jest-dom';
import { Feed } from '../Feed';
import {
feedPosts,
feedPostsWherePinnedAndImagePostsSame,
firstBillboard,
secondBillboard,
thirdBillboard,
podcastEpisodes,
} from './utilities/feedUtilities';
import '../../../assets/javascripts/lib/xss';
import '../../../assets/javascripts/utilities/timeAgo';
global.fetch = fetch;
describe('<Feed /> component', () => {
const getUserData = () => {
return {
followed_tag_names: ['javascript'],
followed_podcast_ids: [1], //should we make this dynamic
profile_image_90: 'mock_url_link',
name: 'firstname lastname',
username: 'username',
reading_list_ids: [],
};
};
beforeAll(() => {
global.userData = jest.fn(() => getUserData());
document.body.setAttribute('data-user', JSON.stringify(getUserData()));
const node = document.createElement('div');
node.setAttribute('id', 'followed-podcasts');
node.setAttribute('data-episodes', JSON.stringify(podcastEpisodes));
document.body.appendChild(node);
});
describe('feedItem organization', () => {
let callback;
beforeAll(() => {
fetch.mockResponseOnce(JSON.stringify(feedPosts), {
headers: { 'content-type': 'application/json' },
});
fetch.mockResponseOnce(firstBillboard, {
headers: { 'content-type': 'text/html' },
});
fetch.mockResponseOnce(secondBillboard, {
headers: { 'content-type': 'text/html' },
});
fetch.mockResponseOnce(thirdBillboard, {
headers: { 'content-type': 'text/html' },
});
callback = jest.fn();
render(<Feed timeFrame="" renderFeed={callback} />);
});
it('should return the correct length of feedItems', async () => {
await waitFor(() => {
const lastCallbackResult =
callback.mock.calls[callback.mock.calls.length - 1][0];
expect(lastCallbackResult.feedItems.length).toEqual(14);
});
});
it('should set the pinnedItem and place it correctly in the feed', async () => {
await waitFor(() => {
const lastCallbackResult =
callback.mock.calls[callback.mock.calls.length - 1][0];
const firstPinnedItem = feedPosts.find((o) => o.pinned === true);
expect(lastCallbackResult.pinnedItem).toEqual(firstPinnedItem);
expect(lastCallbackResult.feedItems[1]).toEqual(firstPinnedItem);
});
});
it('should set the imageItem and place it correctly in the feed', async () => {
await waitFor(() => {
const lastCallbackResult =
callback.mock.calls[callback.mock.calls.length - 1][0];
const firstImageItem = feedPosts.find(
(post) => post.main_image !== null,
);
expect(lastCallbackResult.imageItem).toEqual(firstImageItem);
expect(lastCallbackResult.feedItems[2]).toEqual(firstImageItem);
});
});
it('should place the billboards correctly within the feedItems', async () => {
await waitFor(() => {
const lastCallbackResult =
callback.mock.calls[callback.mock.calls.length - 1][0];
expect(lastCallbackResult.feedItems[0]).toEqual(firstBillboard);
expect(lastCallbackResult.feedItems[3]).toEqual(secondBillboard);
expect(lastCallbackResult.feedItems[9]).toEqual(thirdBillboard);
});
});
it('should place the podcasts correctly within feedItems', async () => {
await waitFor(() => {
const lastCallbackResult =
callback.mock.calls[callback.mock.calls.length - 1][0];
expect(lastCallbackResult.feedItems[4]).toEqual(podcastEpisodes);
});
});
});
describe('when pinned and image posts are the same', () => {
let callback;
beforeAll(() => {
fetch.mockResponseOnce(
JSON.stringify(feedPostsWherePinnedAndImagePostsSame),
{ headers: { 'content-type': 'application/json' } },
);
fetch.mockResponseOnce(firstBillboard, {
headers: { 'content-type': 'text/html' },
});
fetch.mockResponseOnce(secondBillboard, {
headers: { 'content-type': 'text/html' },
});
fetch.mockResponseOnce(thirdBillboard, {
headers: { 'content-type': 'text/html' },
});
callback = jest.fn();
render(<Feed timeFrame="" renderFeed={callback} />);
});
it('should not set a pinned item', async () => {
const lastCallbackResult =
callback.mock.calls[callback.mock.calls.length - 1][0];
const postAndImageItem = feedPostsWherePinnedAndImagePostsSame.find(
(post) => post.main_image !== null && post.pinned === true,
);
expect(lastCallbackResult.pinnedItem).toEqual(null);
expect(lastCallbackResult.imageItem).toEqual(postAndImageItem);
expect(lastCallbackResult.feedItems[1]).toEqual(postAndImageItem);
expect(lastCallbackResult.feedItems[2]).not.toEqual(postAndImageItem);
});
});
describe("when the timeframe prop is 'latest'", () => {
let callback;
beforeAll(() => {
fetch.mockResponseOnce(JSON.stringify(feedPosts), {
headers: { 'content-type': 'application/json' },
});
fetch.mockResponseOnce(firstBillboard, {
headers: { 'content-type': 'text/html' },
});
fetch.mockResponseOnce(secondBillboard, {
headers: { 'content-type': 'text/html' },
});
fetch.mockResponseOnce(thirdBillboard, {
headers: { 'content-type': 'text/html' },
});
callback = jest.fn();
render(<Feed timeFrame="latest" renderFeed={callback} />);
});
it('should not set the pinned items', async () => {
const lastCallbackResult =
callback.mock.calls[callback.mock.calls.length - 1][0];
expect(lastCallbackResult.pinnedItem).toEqual(null);
});
it('should return the correct length of feedItems (by excluding pinned item)', async () => {
await waitFor(() => {
const lastCallbackResult =
callback.mock.calls[callback.mock.calls.length - 1][0];
expect(lastCallbackResult.feedItems.length).toEqual(13);
});
});
});
describe("when we there isn't all three billboards on the home feed", () => {
describe("when there isn't a feed_second billboard", () => {
let callback;
beforeAll(() => {
fetch.mockResponseOnce(JSON.stringify(feedPosts), {
headers: { 'content-type': 'application/json' },
});
fetch.mockResponseOnce(firstBillboard, {
headers: { 'content-type': 'text/html' },
});
fetch.mockResponseOnce(undefined);
fetch.mockResponseOnce(thirdBillboard, {
headers: { 'content-type': 'text/html' },
});
callback = jest.fn();
render(<Feed timeFrame="" renderFeed={callback} />);
});
it('should return the correct length of feedItems', async () => {
await waitFor(() => {
const lastCallbackResult =
callback.mock.calls[callback.mock.calls.length - 1][0];
expect(lastCallbackResult.feedItems.length).toEqual(13);
});
});
it('should still amend the organization of the feedItems correctly', async () => {
await waitFor(() => {
const lastCallbackResult =
callback.mock.calls[callback.mock.calls.length - 1][0];
expect(lastCallbackResult.feedItems[0]).toEqual(firstBillboard);
// there is no second bilboard so podcasts get rendered in 4th place
expect(lastCallbackResult.feedItems[3]).toEqual(podcastEpisodes);
expect(lastCallbackResult.feedItems[8]).toEqual(thirdBillboard);
});
});
});
describe("when there isn't a feed_first or feed_second billboard", () => {
let callback;
beforeAll(() => {
fetch.mockResponseOnce(JSON.stringify(feedPosts), {
headers: { 'content-type': 'application/json' },
});
fetch.mockResponseOnce(undefined);
fetch.mockResponseOnce(undefined);
fetch.mockResponseOnce(thirdBillboard, {
headers: { 'content-type': 'text/html' },
});
callback = jest.fn();
render(<Feed timeFrame="" renderFeed={callback} />);
});
it('should return the correct length of feedItems', async () => {
await waitFor(() => {
const lastCallbackResult =
callback.mock.calls[callback.mock.calls.length - 1][0];
expect(lastCallbackResult.feedItems.length).toEqual(12);
});
});
it('should still amend the organization of the feedItems correctly', async () => {
await waitFor(() => {
const lastCallbackResult =
callback.mock.calls[callback.mock.calls.length - 1][0];
const pinnedItem = feedPosts.find((o) => o.pinned === true);
// there is no first billboard
expect(lastCallbackResult.feedItems[0]).toEqual(pinnedItem);
// there is no second bilboard so podcasts get rendered in 3rd place
expect(lastCallbackResult.feedItems[2]).toEqual(podcastEpisodes);
expect(lastCallbackResult.feedItems[7]).toEqual(thirdBillboard);
});
});
});
describe('when items that we fetch for the feed throw an error', () => {
const callback = jest.fn();
beforeAll(() => {
global.Honeybadger = { notify: jest.fn() };
fetch.mockResponseOnce(JSON.stringify(feedPosts), {
headers: { 'content-type': 'application/json' },
});
fetch.mockRejectOnce();
fetch.mockRejectOnce();
fetch.mockResponseOnce(thirdBillboard, {
headers: { 'content-type': 'text/html' },
});
render(<Feed timeFrame="" renderFeed={callback} />);
});
it('should render and return the other feedItems', async () => {
await waitFor(() => {
const lastCallbackResult =
callback.mock.calls[callback.mock.calls.length - 1][0];
expect(lastCallbackResult.feedItems.length).toEqual(12);
});
});
it('should organize the feedItems correctly', async () => {
await waitFor(() => {
const lastCallbackResult =
callback.mock.calls[callback.mock.calls.length - 1][0];
const pinnedItem = feedPosts.find((o) => o.pinned === true);
// we will not be rendering the first billboard since it errored
expect(lastCallbackResult.feedItems[0]).toEqual(pinnedItem);
// we will not be rendering the second billboard since it errored
// so podcasts get rendered in 3rd place
expect(lastCallbackResult.feedItems[2]).toEqual(podcastEpisodes);
expect(lastCallbackResult.feedItems[7]).toEqual(thirdBillboard);
});
});
});
});
});