-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test(headless SSR): add unit tests for recommendation factories (#4786)
- Loading branch information
Showing
7 changed files
with
587 additions
and
92 deletions.
There are no files selected for viewing
183 changes: 183 additions & 0 deletions
183
packages/headless/src/app/commerce-ssr-engine/factories/build-factory.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
import {Mock, describe, it, expect, vi} from 'vitest'; | ||
import {defineRecommendations} from '../../../controllers/commerce/recommendations/headless-recommendations.ssr.js'; | ||
import {getSampleCommerceEngineConfiguration} from '../../commerce-engine/commerce-engine-configuration.js'; | ||
import * as commerceEngine from '../../commerce-engine/commerce-engine.js'; | ||
import {CommerceEngineOptions} from '../../commerce-engine/commerce-engine.js'; | ||
import {buildLogger} from '../../logger.js'; | ||
import {SolutionType} from '../types/common.js'; | ||
import {buildFactory} from './build-factory.js'; | ||
|
||
vi.mock('../../logger.js'); | ||
|
||
describe('buildFactory', () => { | ||
const mockLogger = { | ||
warn: vi.fn(), | ||
debug: vi.fn(), | ||
}; | ||
|
||
const engineOptions: CommerceEngineOptions = { | ||
configuration: getSampleCommerceEngineConfiguration(), | ||
navigatorContextProvider: vi.fn(), | ||
}; | ||
|
||
const mockControllerDefinitions = {}; | ||
|
||
beforeEach(() => { | ||
const actualImplementation = commerceEngine.buildCommerceEngine; | ||
vi.spyOn(commerceEngine, 'buildCommerceEngine').mockImplementation( | ||
actualImplementation | ||
); | ||
(buildLogger as Mock).mockReturnValue(mockLogger); | ||
}); | ||
|
||
afterEach(() => { | ||
vi.clearAllMocks(); | ||
}); | ||
|
||
it('should warn if navigatorContextProvider is missing', async () => { | ||
const factory = buildFactory(mockControllerDefinitions, { | ||
configuration: getSampleCommerceEngineConfiguration(), | ||
}); | ||
const build = factory(SolutionType.listing); | ||
|
||
await build(); | ||
|
||
expect(mockLogger.warn).toHaveBeenCalledWith( | ||
expect.stringContaining('Missing navigator context in server-side code') | ||
); | ||
}); | ||
|
||
it('should throw an error for unsupported solution type', async () => { | ||
const factory = buildFactory(mockControllerDefinitions, engineOptions); | ||
const build = factory('unsupported' as SolutionType); | ||
|
||
await expect(build()).rejects.toThrow('Unsupported solution type'); | ||
}); | ||
|
||
describe('when building for standalone', () => { | ||
const factory = buildFactory(mockControllerDefinitions, engineOptions); | ||
const build = factory(SolutionType.standalone); | ||
|
||
it('should build SSRCommerceEngine with standalone solution type', async () => { | ||
const factory = buildFactory(mockControllerDefinitions, engineOptions); | ||
const build = factory(SolutionType.standalone); | ||
const result = await build(); | ||
|
||
expect(result.engine).toBeDefined(); | ||
expect(result.controllers).toBeDefined(); | ||
}); | ||
|
||
it('should never add middlewares', async () => { | ||
await build(); | ||
expect( | ||
(commerceEngine.buildCommerceEngine as Mock).mock.calls[0][0] | ||
.middlewares | ||
).toHaveLength(0); | ||
}); | ||
}); | ||
|
||
describe('when building for search', () => { | ||
const factory = buildFactory(mockControllerDefinitions, engineOptions); | ||
const build = factory(SolutionType.search); | ||
|
||
it('should build SSRCommerceEngine with search solution type', async () => { | ||
const factory = buildFactory(mockControllerDefinitions, engineOptions); | ||
const build = factory(SolutionType.search); | ||
const result = await build(); | ||
|
||
expect(result.engine).toBeDefined(); | ||
expect(result.controllers).toBeDefined(); | ||
}); | ||
|
||
it('should always add a single middleware', async () => { | ||
await build(); | ||
expect( | ||
(commerceEngine.buildCommerceEngine as Mock).mock.calls[0][0] | ||
.middlewares | ||
).toHaveLength(1); | ||
}); | ||
}); | ||
|
||
describe('when building for listing', () => { | ||
const factory = buildFactory(mockControllerDefinitions, engineOptions); | ||
const build = factory(SolutionType.listing); | ||
|
||
it('should build SSRCommerceEngine with listing solution type', async () => { | ||
const factory = buildFactory(mockControllerDefinitions, engineOptions); | ||
const build = factory(SolutionType.listing); | ||
const result = await build(); | ||
|
||
expect(result.engine).toBeDefined(); | ||
expect(result.controllers).toBeDefined(); | ||
}); | ||
|
||
it('should always add a single middleware', async () => { | ||
await build(); | ||
expect( | ||
(commerceEngine.buildCommerceEngine as Mock).mock.calls[0][0] | ||
.middlewares | ||
).toHaveLength(1); | ||
}); | ||
}); | ||
|
||
describe('when building for recommendations', () => { | ||
const controllerDefinition = { | ||
popularViewed: defineRecommendations({ | ||
options: { | ||
slotId: 'slot_1', | ||
}, | ||
}), | ||
popularBought: defineRecommendations({ | ||
options: { | ||
slotId: 'slot_2', | ||
}, | ||
}), | ||
}; | ||
|
||
const factory = buildFactory(controllerDefinition, engineOptions); | ||
const build = factory(SolutionType.recommendation); | ||
|
||
it('should build SSRCommerceEngine with recommendation solution type', async () => { | ||
const factory = buildFactory(mockControllerDefinitions, engineOptions); | ||
const build = factory(SolutionType.recommendation); | ||
const result = await build(); | ||
|
||
expect(result.engine).toBeDefined(); | ||
expect(result.controllers).toBeDefined(); | ||
}); | ||
|
||
it('should not add middleware if not specified otherwise', async () => { | ||
await build({controllers: {}}); | ||
expect( | ||
(commerceEngine.buildCommerceEngine as Mock).mock.calls[0][0] | ||
.middlewares | ||
).toHaveLength(0); | ||
}); | ||
|
||
it('should add a middleware for each enabled recommendation', async () => { | ||
await build({ | ||
controllers: { | ||
popularBought: {enabled: true}, | ||
popularViewed: {enabled: true}, | ||
}, | ||
}); | ||
expect( | ||
(commerceEngine.buildCommerceEngine as Mock).mock.calls[0][0] | ||
.middlewares | ||
).toHaveLength(2); | ||
}); | ||
|
||
it('should not add middlewares if user disabled them', async () => { | ||
await build({ | ||
controllers: { | ||
popularBought: {enabled: false}, | ||
popularViewed: {enabled: false}, | ||
}, | ||
}); | ||
expect( | ||
(commerceEngine.buildCommerceEngine as Mock).mock.calls[0][0] | ||
.middlewares | ||
).toHaveLength(0); | ||
}); | ||
}); | ||
}); |
104 changes: 104 additions & 0 deletions
104
...dless/src/app/commerce-ssr-engine/factories/recommendation-hydrated-state-factory.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import {describe, it, expect, vi} from 'vitest'; | ||
import {defineRecommendations} from '../../../controllers/commerce/recommendations/headless-recommendations.ssr.js'; | ||
import {getSampleCommerceEngineConfiguration} from '../../commerce-engine/commerce-engine-configuration.js'; | ||
import {SolutionType} from '../types/common.js'; | ||
import {buildFactory} from './build-factory.js'; | ||
import {hydratedRecommendationStaticStateFactory} from './recommendation-hydrated-state-factory.js'; | ||
|
||
vi.mock('./build-factory.js'); | ||
|
||
describe('hydratedRecommendationStaticStateFactory', () => { | ||
const createEngineOptions = () => ({ | ||
configuration: getSampleCommerceEngineConfiguration(), | ||
navigatorContextProvider: vi.fn(), | ||
}); | ||
|
||
const createControllerDefinitions = () => ({ | ||
popularViewed: defineRecommendations({ | ||
options: { | ||
slotId: 'slot_1', | ||
}, | ||
}), | ||
popularBought: defineRecommendations({ | ||
options: { | ||
slotId: 'slot_2', | ||
}, | ||
}), | ||
}); | ||
const mockBuildResult = { | ||
engine: { | ||
dispatch: vi.fn(), | ||
waitForRequestCompletedAction: vi | ||
.fn() | ||
.mockResolvedValue([{type: 'some-action'}]), | ||
}, | ||
controllers: {}, | ||
}; | ||
const mockSearchActions = [{type: 'some-search-action'}]; | ||
|
||
beforeEach(() => { | ||
vi.clearAllMocks(); | ||
}); | ||
|
||
it('should call buildFactory with correct parameters', async () => { | ||
const mockSolutionTypeBuild = vi.fn().mockResolvedValue(mockBuildResult); | ||
const controllerDefinitions = createControllerDefinitions(); | ||
const options = createEngineOptions(); | ||
const mockRecommendationState = vi | ||
.fn() | ||
.mockImplementation(() => mockSolutionTypeBuild); | ||
vi.mocked(buildFactory).mockReturnValue(mockRecommendationState); | ||
|
||
const factory = hydratedRecommendationStaticStateFactory( | ||
controllerDefinitions, | ||
options | ||
); | ||
|
||
await factory({searchActions: mockSearchActions, controllers: {}}); | ||
|
||
expect(buildFactory).toHaveBeenCalledWith(controllerDefinitions, options); | ||
expect(mockRecommendationState).toHaveBeenCalledWith( | ||
SolutionType.recommendation | ||
); | ||
}); | ||
|
||
it('should dispatch search actions and wait for request completion', async () => { | ||
const mockSolutionTypeBuild = vi.fn().mockResolvedValue(mockBuildResult); | ||
vi.mocked(buildFactory).mockReturnValue(() => mockSolutionTypeBuild); | ||
|
||
const factory = hydratedRecommendationStaticStateFactory( | ||
createControllerDefinitions(), | ||
createEngineOptions() | ||
); | ||
|
||
const staticState = await factory({ | ||
searchActions: mockSearchActions, | ||
controllers: {}, | ||
}); | ||
|
||
expect(mockBuildResult.engine.dispatch).toHaveBeenCalledWith( | ||
mockSearchActions[0] | ||
); | ||
expect( | ||
mockBuildResult.engine.waitForRequestCompletedAction | ||
).toHaveBeenCalledOnce(); | ||
expect(staticState).toEqual(mockBuildResult); | ||
}); | ||
|
||
it('should call fromBuildResult with correct parameters', async () => { | ||
const mockSolutionTypeBuild = vi.fn().mockResolvedValue(mockBuildResult); | ||
vi.mocked(buildFactory).mockReturnValue(() => mockSolutionTypeBuild); | ||
|
||
const factory = hydratedRecommendationStaticStateFactory( | ||
createControllerDefinitions(), | ||
createEngineOptions() | ||
); | ||
|
||
const staticState = await factory({ | ||
searchActions: mockSearchActions, | ||
controllers: {}, | ||
}); | ||
|
||
expect(staticState).toEqual(mockBuildResult); | ||
}); | ||
}); |
Oops, something went wrong.