Skip to content

Commit

Permalink
fix(react|redux): commerce pages in ssr not working
Browse files Browse the repository at this point in the history
  • Loading branch information
richard190m committed Jan 10, 2024
1 parent 44bcf36 commit d0b2abc
Show file tree
Hide file tree
Showing 14 changed files with 143 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
mockCommercePagesInitialState,
mockCommercePagesLoadingState,
mockCommercePagesState,
slug,
} from 'tests/__fixtures__/contents/index.mjs';
import { fetchCommercePages } from '@farfetch/blackout-redux';
import { withStore } from '../../../../tests/helpers/index.js';
Expand Down Expand Up @@ -84,6 +85,7 @@ describe('useCommercePages', () => {

expect(fetchCommercePages).toHaveBeenCalledWith(
{
slug,
brand: commercePageQuery.brand,
category: commercePageQuery.category,
contentTypeCode: commercePageQuery.contentTypeCode,
Expand Down Expand Up @@ -124,6 +126,7 @@ describe('useCommercePages', () => {

expect(fetchCommercePages).toHaveBeenCalledWith(
{
slug,
brand: commercePageQuery.brand,
category: commercePageQuery.category,
contentTypeCode: commercePageQuery.contentTypeCode,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { cleanup, renderHook } from '@testing-library/react';
import { ContentPageType } from '@farfetch/blackout-client';
import { fetchContentPage } from '@farfetch/blackout-redux';
import {
mockCommercePageWithDataState,
mockContentPageEntry,
mockContentPageErrorState,
mockContentPageInitialState,
Expand Down Expand Up @@ -102,6 +103,34 @@ describe('useContentPage', () => {

expect(fetchContentPage).not.toHaveBeenCalled();
});

it('should return values correctly with commercePagesHash option', () => {
const { result } = renderHook(
() =>
useContentPage(
ContentPageType.Listing,
{ slug },
{ isCommercePage: true },
),
{
wrapper: withStore(mockCommercePageWithDataState),
},
);

expect(result.current).toStrictEqual({
data: [
mockCommercePageWithDataState.entities.contents[
mockContentPageEntry.publicationId
],
],
isLoading: false,
error: null,
isFetched: true,
actions: {
fetch: expect.any(Function),
},
});
});
});

describe('actions', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { CommercePagesRankingStrategy } from '@farfetch/blackout-redux';
import type { Config, QueryCommercePages } from '@farfetch/blackout-client';

export interface UseCommercePagesOptions extends QueryCommercePages {
slug: string;
enableAutoFetch?: boolean;
strategy?: CommercePagesRankingStrategy;
fetchConfig?: Config;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ import type { Config } from '@farfetch/blackout-client';
export interface UseContentPageOptions {
enableAutoFetch?: boolean;
fetchConfig?: Config;
isCommercePage?: boolean;
}
2 changes: 1 addition & 1 deletion packages/react/src/contents/hooks/useCommercePages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const useCommercePages = <T = ComponentType[]>(
const query = useMemo(
() => ({
contentTypeCode: ContentTypeCode.CommercePages,
...fetchQuery,
codes: fetchQuery.slug,
}),
[fetchQuery],
);
Expand Down
14 changes: 10 additions & 4 deletions packages/react/src/contents/hooks/useContentPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,20 @@ const useContentPage = <T = [ComponentType]>(
) => {
const store = useStore();

const { enableAutoFetch = true, fetchConfig } = options;
const {
enableAutoFetch = true,
fetchConfig,
isCommercePage = false,
} = options;

const query = useMemo(
() => ({
contentTypeCode: ContentTypeCode.ContentPage,
codes: fetchQuery.slug.split('?')[0] as string,
contentTypeCode: isCommercePage
? ContentTypeCode.CommercePages
: ContentTypeCode.ContentPage,
codes: fetchQuery.slug,
}),
[fetchQuery.slug],
[fetchQuery.slug, isCommercePage],
);

const fetchQueryWithoutSlug = useMemo(() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import * as actionTypes from '../../actionTypes.js';
import * as normalizr from 'normalizr';
import {
commercePagesQuery,
commercePageQuery,
commercePageQueryWithoutSlug,
expectedCommercePagesNormalizedPayload,
mockCommercePages,
} from 'tests/__fixtures__/contents/index.mjs';
Expand Down Expand Up @@ -44,12 +45,12 @@ describe('fetchCommercePages() action creator', () => {
(getCommercePages as jest.Mock).mockRejectedValueOnce(expectedError);

await expect(
async () => await fetchCommercePages(commercePagesQuery)(store.dispatch),
async () => await fetchCommercePages(commercePageQuery)(store.dispatch),
).rejects.toThrow(expectedError);

expect(getCommercePages).toHaveBeenCalledTimes(1);
expect(getCommercePages).toHaveBeenCalledWith(
commercePagesQuery,
commercePageQueryWithoutSlug,
expectedConfig,
);
expect(store.getActions()).toEqual([
Expand All @@ -72,7 +73,7 @@ describe('fetchCommercePages() action creator', () => {
it('should create the correct actions for when the get commerce pages procedure is successful', async () => {
(getCommercePages as jest.Mock).mockResolvedValueOnce(mockCommercePages);

await fetchCommercePages(commercePagesQuery)(store.dispatch).then(
await fetchCommercePages(commercePageQuery)(store.dispatch).then(
clientResult => {
expect(clientResult).toBe(mockCommercePages);
},
Expand All @@ -83,7 +84,7 @@ describe('fetchCommercePages() action creator', () => {
expect(normalizeSpy).toHaveBeenCalledTimes(1);
expect(getCommercePages).toHaveBeenCalledTimes(1);
expect(getCommercePages).toHaveBeenCalledWith(
commercePagesQuery,
commercePageQueryWithoutSlug,
expectedConfig,
);
expect(actionResults).toMatchObject([
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ import {
type CommercePages,
type Config,
type GetCommercePages,
type QueryCommercePages,
toBlackoutError,
} from '@farfetch/blackout-client';
import {
type CommercePagesRankingStrategy,
ContentTypeCode,
type FetchCommercePagesAction,
type QueryCommercePagesWithSlug,
} from '../../types/index.js';
import { contentEntries } from '../../../entities/schemas/content.js';
import { normalize } from 'normalizr';
Expand All @@ -29,7 +29,7 @@ import type { Dispatch } from 'redux';
const fetchCommercePagesFactory =
(getCommercePages: GetCommercePages) =>
(
query: QueryCommercePages,
query: QueryCommercePagesWithSlug,
strategy?: CommercePagesRankingStrategy,
config?: Config,
) =>
Expand All @@ -39,17 +39,19 @@ const fetchCommercePagesFactory =
let hash: string | undefined;

try {
const { slug, ...queryWithoutSlug } = query;

hash = generateContentHash({
contentTypeCode: ContentTypeCode.CommercePages,
...query,
codes: slug,
});

dispatch({
payload: { hash },
type: actionTypes.FETCH_COMMERCE_PAGES_REQUEST,
});

const result = await getCommercePages(query, config);
const result = await getCommercePages(queryWithoutSlug, config);
const rankedResult = applyCommercePagesRankingStrategy(result, strategy);

dispatch({
Expand Down
38 changes: 24 additions & 14 deletions packages/redux/src/contents/serverInitialState.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import {
applyCommercePagesRankingStrategy,
generateContentHash,
} from './utils.js';
import { buildQueryStringFromObject } from '../helpers/index.js';
import { contentEntries } from '../entities/schemas/content.js';
import { generateContentHash } from './utils.js';
import { type ContentsState } from './types/index.js';
import { get, merge } from 'lodash-es';
import { INITIAL_STATE_CONTENT } from './reducer.js';
import { normalize } from 'normalizr';
import parse from 'url-parse';
import type { ContentsState } from './types/index.js';
import type { ServerInitialState } from '../types/serverInitialState.types.js';

/**
Expand All @@ -15,28 +18,36 @@ import type { ServerInitialState } from '../types/serverInitialState.types.js';
*
* @returns Initial state for the contents reducer.
*/
const serverInitialState: ServerInitialState = ({ model }) => {
const serverInitialState: ServerInitialState = ({ model, strategy }) => {
if (!get(model, 'searchContentRequests')) {
return { contents: INITIAL_STATE_CONTENT };
}

const { searchContentRequests, slug, seoMetadata, subfolder } = model;
const url = subfolder !== '/' ? slug?.replace(subfolder, '') : slug;
const normalizedUrl = url
?.replace('?json=true', '')
.replace('&json=true', '');

const contents = searchContentRequests.reduce((acc, item) => {
const { searchResponse } = item;
const firstSearchResponseItem = searchResponse.entries[0];

if (!firstSearchResponseItem) {
return acc;
}

const {
searchResponse,
filters: { codes, contentTypeCode },
} = item;
let response = searchResponse;
const isCommercePage = contentTypeCode === 'commerce_pages';
const code = isCommercePage ? normalizedUrl : codes?.[0];
const hash = generateContentHash({
codes: firstSearchResponseItem.code,
contentTypeCode: firstSearchResponseItem.contentTypeCode,
codes: code,
contentTypeCode: contentTypeCode,
});

if (isCommercePage) {
response = applyCommercePagesRankingStrategy(searchResponse, strategy);
}

const { entities, result } = {
...normalize({ hash, ...searchResponse }, contentEntries),
...normalize({ hash, ...response }, contentEntries),
};

return merge(acc, {
Expand All @@ -53,7 +64,6 @@ const serverInitialState: ServerInitialState = ({ model }) => {
});
}, {});

const url = subfolder !== '/' ? slug?.replace(subfolder, '') : slug;
const { pathname, query } = parse(url, true);

delete query.json;
Expand Down
5 changes: 5 additions & 0 deletions packages/redux/src/contents/types/actions.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {
BlackoutError,
Contents,
ContentType,
QueryCommercePages,
SEOMetadata,
} from '@farfetch/blackout-client';
import type { ContentEntity } from '../../entities/index.js';
Expand All @@ -22,6 +23,10 @@ export type ContentsPayload = NormalizedSchema<
ContentsNormalized
> & { hash: Hash };

export interface QueryCommercePagesWithSlug extends QueryCommercePages {
slug: string;
}

/**
* Fetch Content Page Action
*/
Expand Down
2 changes: 2 additions & 0 deletions packages/redux/src/types/serverInitialState.types.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import type { CommercePagesRankingStrategy } from '../contents/types/commercePagesRankingStrategy.types.js';
import type { LocaleState } from '../locale/index.js';
import type { Model } from './model.types.js';
import type { ProductsState } from '../products/index.js';
import type { StoreState } from './storeState.types.js';

export type ServerInitialState = (data: {
model: Model;
strategy?: CommercePagesRankingStrategy;
options?: { productImgQueryParam?: string };
}) => Omit<StoreState, 'products' | 'locale'> & {
products?: Partial<ProductsState>;
Expand Down
20 changes: 11 additions & 9 deletions tests/__fixtures__/contents/commercePages.fixtures.mts
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,28 @@ import {
generateContentHash,
} from '@farfetch/blackout-redux';

export const commercePagesQuery = {
type: CommercePagesType.Listing,
gender: 0,
brand: 5030844,
category: '136643',
};

export const slug = 'woman/gucci';
export const commercePageQuery = {

export const commercePageQueryWithoutSlug = {
brand: 5030844,
category: '136643',
type: CommercePagesType.Listing,
gender: GenderCode.Woman,
contentTypeCode: 'commerce_pages',
};

export const commercePageQuery = {
slug,
...commercePageQueryWithoutSlug,
};

export const commercePageContentPublicationId =
'dc9c0c95-9485-45c2-a76c-6923bb39b544';

export const commercePagesHash = generateContentHash(commercePageQuery);
export const commercePagesHash = generateContentHash({
contentTypeCode: commercePageQuery.contentTypeCode,
codes: commercePageQuery.slug,
});

export const mockCommercePages = {
number: 1,
Expand Down
Loading

0 comments on commit d0b2abc

Please sign in to comment.