From d0b2abc0b8bfed5215b895e582c567bb34368c69 Mon Sep 17 00:00:00 2001 From: Ricardo Mota Date: Mon, 11 Dec 2023 15:04:46 +0000 Subject: [PATCH] fix(react|redux): commerce pages in ssr not working --- .../hooks/__tests__/useCommercePages.test.tsx | 3 ++ .../hooks/__tests__/useContentPage.test.tsx | 29 +++++++++++++ .../hooks/types/useCommercePages.types.ts | 1 + .../hooks/types/useContentPage.types.ts | 1 + .../src/contents/hooks/useCommercePages.ts | 2 +- .../src/contents/hooks/useContentPage.ts | 14 ++++-- .../__tests__/fetchCommercePages.test.ts | 11 ++--- .../factories/fetchCommercePagesFactory.ts | 10 +++-- .../redux/src/contents/serverInitialState.ts | 38 ++++++++++------ .../redux/src/contents/types/actions.types.ts | 5 +++ .../src/types/serverInitialState.types.ts | 2 + .../contents/commercePages.fixtures.mts | 20 +++++---- .../contents/contentPage.fixtures.mts | 43 +++++++++++++++++++ .../contents/contents.fixtures.mts | 2 +- 14 files changed, 143 insertions(+), 38 deletions(-) diff --git a/packages/react/src/contents/hooks/__tests__/useCommercePages.test.tsx b/packages/react/src/contents/hooks/__tests__/useCommercePages.test.tsx index f6ac7bdef..0aa4672f5 100644 --- a/packages/react/src/contents/hooks/__tests__/useCommercePages.test.tsx +++ b/packages/react/src/contents/hooks/__tests__/useCommercePages.test.tsx @@ -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'; @@ -84,6 +85,7 @@ describe('useCommercePages', () => { expect(fetchCommercePages).toHaveBeenCalledWith( { + slug, brand: commercePageQuery.brand, category: commercePageQuery.category, contentTypeCode: commercePageQuery.contentTypeCode, @@ -124,6 +126,7 @@ describe('useCommercePages', () => { expect(fetchCommercePages).toHaveBeenCalledWith( { + slug, brand: commercePageQuery.brand, category: commercePageQuery.category, contentTypeCode: commercePageQuery.contentTypeCode, diff --git a/packages/react/src/contents/hooks/__tests__/useContentPage.test.tsx b/packages/react/src/contents/hooks/__tests__/useContentPage.test.tsx index 8be2fe33c..69940dfc8 100644 --- a/packages/react/src/contents/hooks/__tests__/useContentPage.test.tsx +++ b/packages/react/src/contents/hooks/__tests__/useContentPage.test.tsx @@ -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, @@ -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', () => { diff --git a/packages/react/src/contents/hooks/types/useCommercePages.types.ts b/packages/react/src/contents/hooks/types/useCommercePages.types.ts index 06a6c8187..532cfbfe7 100644 --- a/packages/react/src/contents/hooks/types/useCommercePages.types.ts +++ b/packages/react/src/contents/hooks/types/useCommercePages.types.ts @@ -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; diff --git a/packages/react/src/contents/hooks/types/useContentPage.types.ts b/packages/react/src/contents/hooks/types/useContentPage.types.ts index d205197f8..11a71995c 100644 --- a/packages/react/src/contents/hooks/types/useContentPage.types.ts +++ b/packages/react/src/contents/hooks/types/useContentPage.types.ts @@ -3,4 +3,5 @@ import type { Config } from '@farfetch/blackout-client'; export interface UseContentPageOptions { enableAutoFetch?: boolean; fetchConfig?: Config; + isCommercePage?: boolean; } diff --git a/packages/react/src/contents/hooks/useCommercePages.ts b/packages/react/src/contents/hooks/useCommercePages.ts index d9d6a5f0d..da693800e 100644 --- a/packages/react/src/contents/hooks/useCommercePages.ts +++ b/packages/react/src/contents/hooks/useCommercePages.ts @@ -29,7 +29,7 @@ const useCommercePages = ( const query = useMemo( () => ({ contentTypeCode: ContentTypeCode.CommercePages, - ...fetchQuery, + codes: fetchQuery.slug, }), [fetchQuery], ); diff --git a/packages/react/src/contents/hooks/useContentPage.ts b/packages/react/src/contents/hooks/useContentPage.ts index d4b0189e0..42dd07574 100644 --- a/packages/react/src/contents/hooks/useContentPage.ts +++ b/packages/react/src/contents/hooks/useContentPage.ts @@ -24,14 +24,20 @@ const useContentPage = ( ) => { 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(() => { diff --git a/packages/redux/src/contents/actions/__tests__/fetchCommercePages.test.ts b/packages/redux/src/contents/actions/__tests__/fetchCommercePages.test.ts index b912166a8..c9fc3ad85 100644 --- a/packages/redux/src/contents/actions/__tests__/fetchCommercePages.test.ts +++ b/packages/redux/src/contents/actions/__tests__/fetchCommercePages.test.ts @@ -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'; @@ -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([ @@ -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); }, @@ -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([ diff --git a/packages/redux/src/contents/actions/factories/fetchCommercePagesFactory.ts b/packages/redux/src/contents/actions/factories/fetchCommercePagesFactory.ts index e54c71665..4ff5288c0 100644 --- a/packages/redux/src/contents/actions/factories/fetchCommercePagesFactory.ts +++ b/packages/redux/src/contents/actions/factories/fetchCommercePagesFactory.ts @@ -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'; @@ -29,7 +29,7 @@ import type { Dispatch } from 'redux'; const fetchCommercePagesFactory = (getCommercePages: GetCommercePages) => ( - query: QueryCommercePages, + query: QueryCommercePagesWithSlug, strategy?: CommercePagesRankingStrategy, config?: Config, ) => @@ -39,9 +39,11 @@ const fetchCommercePagesFactory = let hash: string | undefined; try { + const { slug, ...queryWithoutSlug } = query; + hash = generateContentHash({ contentTypeCode: ContentTypeCode.CommercePages, - ...query, + codes: slug, }); dispatch({ @@ -49,7 +51,7 @@ const fetchCommercePagesFactory = type: actionTypes.FETCH_COMMERCE_PAGES_REQUEST, }); - const result = await getCommercePages(query, config); + const result = await getCommercePages(queryWithoutSlug, config); const rankedResult = applyCommercePagesRankingStrategy(result, strategy); dispatch({ diff --git a/packages/redux/src/contents/serverInitialState.ts b/packages/redux/src/contents/serverInitialState.ts index 9a8b47f8b..47c2047a8 100644 --- a/packages/redux/src/contents/serverInitialState.ts +++ b/packages/redux/src/contents/serverInitialState.ts @@ -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'; /** @@ -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, { @@ -53,7 +64,6 @@ const serverInitialState: ServerInitialState = ({ model }) => { }); }, {}); - const url = subfolder !== '/' ? slug?.replace(subfolder, '') : slug; const { pathname, query } = parse(url, true); delete query.json; diff --git a/packages/redux/src/contents/types/actions.types.ts b/packages/redux/src/contents/types/actions.types.ts index 2f17e5696..ffb68bf63 100644 --- a/packages/redux/src/contents/types/actions.types.ts +++ b/packages/redux/src/contents/types/actions.types.ts @@ -4,6 +4,7 @@ import type { BlackoutError, Contents, ContentType, + QueryCommercePages, SEOMetadata, } from '@farfetch/blackout-client'; import type { ContentEntity } from '../../entities/index.js'; @@ -22,6 +23,10 @@ export type ContentsPayload = NormalizedSchema< ContentsNormalized > & { hash: Hash }; +export interface QueryCommercePagesWithSlug extends QueryCommercePages { + slug: string; +} + /** * Fetch Content Page Action */ diff --git a/packages/redux/src/types/serverInitialState.types.ts b/packages/redux/src/types/serverInitialState.types.ts index 239512338..e79ae7eaa 100644 --- a/packages/redux/src/types/serverInitialState.types.ts +++ b/packages/redux/src/types/serverInitialState.types.ts @@ -1,3 +1,4 @@ +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'; @@ -5,6 +6,7 @@ import type { StoreState } from './storeState.types.js'; export type ServerInitialState = (data: { model: Model; + strategy?: CommercePagesRankingStrategy; options?: { productImgQueryParam?: string }; }) => Omit & { products?: Partial; diff --git a/tests/__fixtures__/contents/commercePages.fixtures.mts b/tests/__fixtures__/contents/commercePages.fixtures.mts index f2cac2382..651e3189a 100644 --- a/tests/__fixtures__/contents/commercePages.fixtures.mts +++ b/tests/__fixtures__/contents/commercePages.fixtures.mts @@ -9,15 +9,9 @@ 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, @@ -25,10 +19,18 @@ export const commercePageQuery = { 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, diff --git a/tests/__fixtures__/contents/contentPage.fixtures.mts b/tests/__fixtures__/contents/contentPage.fixtures.mts index 1c7e1a0c9..74614e611 100644 --- a/tests/__fixtures__/contents/contentPage.fixtures.mts +++ b/tests/__fixtures__/contents/contentPage.fixtures.mts @@ -15,6 +15,12 @@ const contentQuery = { export const contentPagesHash = generateContentHash(contentQuery); +const commerceQuery = { + codes: slugContentWithoutQuery, + contentTypeCode: 'commerce_pages', +}; +const commercePagesHash = generateContentHash(commerceQuery); + export const mockContentPageEntry = { publicationId: 'dc9c0c95-9485-45c2-a76c-6923bb39b544', versionId: '78f1922d-0ef1-46ed-b02c-ca541d0a0d80', @@ -65,6 +71,11 @@ export const mockContentPageEntry = { ], }; +const mockCommercePageEntry = { + ...mockContentPageEntry, + contentTypeCode: 'commerce_pages', +}; + export const mockContentPage = { number: 1, totalItems: 1, @@ -153,3 +164,35 @@ export const mockContentPageWithDataState = { }, }, }; + +export const mockCommercePageWithDataState = { + entities: { + contents: { + [mockCommercePageEntry.publicationId]: { + ...mockCommercePageEntry, + publicationDate: 0, + metadata: { + ...mockCommercePageEntry.metadata, + custom: { + ...mockCommercePageEntry.metadata.custom, + eventDate: 0, + }, + }, + }, + }, + }, + contents: { + ...mockContentPageInitialState.contents, + searchResults: { + [commercePagesHash]: { + isLoading: false, + error: null, + result: { + hash: commercePagesHash, + ...mockContentPage, + entries: [mockCommercePageEntry.publicationId], + }, + }, + }, + }, +}; diff --git a/tests/__fixtures__/contents/contents.fixtures.mts b/tests/__fixtures__/contents/contents.fixtures.mts index e2fa9c41c..aaa922e90 100644 --- a/tests/__fixtures__/contents/contents.fixtures.mts +++ b/tests/__fixtures__/contents/contents.fixtures.mts @@ -319,7 +319,7 @@ export const mockModel = { { filters: { spaceCode: 'website', - codes: 'career-test', + codes: ['career-test'], contentTypeCode: 'careers', environmentCode: 'live', sort: 'publicationDate:desc',