Skip to content

Commit

Permalink
feat(productlistings): create plpv2 interactive controller
Browse files Browse the repository at this point in the history
  • Loading branch information
Spuffynism committed Aug 24, 2023
1 parent 68c67b0 commit a24804a
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import {ProductRecommendation} from '../../../../api/search/search/product-recommendation';
import {configuration} from '../../../../app/common-reducers';
import {
buildMockCommerceEngine,
MockCommerceEngine,
} from '../../../../test/mock-engine';
import {buildMockProductRecommendation} from '../../../../test/mock-product-recommendation';
import {logProductRecommendationOpen} from '../../product-listing-analytics';
import {pushRecentResult} from '../../product-listing-recent-results';
import {
buildInteractiveResult,
InteractiveResult,
} from './product-listing-v2-interactive-result';

describe('InteractiveResult', () => {
let engine: MockCommerceEngine;
let mockProductRec: ProductRecommendation;
let interactiveResult: InteractiveResult;
let logDocumentOpenPendingActionType: string;

const productRecStringParams = {
permanentid: 'permanentid',
documentUri: 'documentUri',
clickUri: 'clickUri',
};

function initializeInteractiveResult(delay?: number) {
const productRec = (mockProductRec = buildMockProductRecommendation(
productRecStringParams
));
logDocumentOpenPendingActionType =
logProductRecommendationOpen(mockProductRec).pending.type;
interactiveResult = buildInteractiveResult(engine, {
options: {result: productRec, selectionDelay: delay},
});
}

function findLogDocumentAction() {
return (
engine.actions.find(
(action) => action.type === logDocumentOpenPendingActionType
) ?? null
);
}

function expectLogDocumentActionPending() {
const action = findLogDocumentAction();
expect(action).toEqual(
logProductRecommendationOpen(mockProductRec).pending(
action!.meta.requestId
)
);
}

beforeEach(() => {
engine = buildMockCommerceEngine();
initializeInteractiveResult();
jest.useFakeTimers();
});

afterEach(() => {
jest.useRealTimers();
});

it('adds the correct reducers to engine', () => {
expect(engine.addReducers).toHaveBeenCalledWith({configuration});
});

it('when calling select() should add the result to recent results list', () => {
interactiveResult.select();
jest.runAllTimers();

expect(
engine.actions.find((a) => a.type === pushRecentResult.type)
).toBeDefined();
});

it('when calling select(), logs documentOpen', () => {
interactiveResult.select();
expectLogDocumentActionPending();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import {ProductRecommendation} from '../../../../api/search/search/product-recommendation';
import {CommerceEngine} from '../../../../app/commerce-engine/commerce-engine';
import {
InteractiveResultCore,
InteractiveResultCoreOptions,
InteractiveResultCoreProps,
} from '../../../../controllers';
import {buildInteractiveResultCore} from '../../../../controllers/core/interactive-result/headless-core-interactive-result';
import {logProductRecommendationOpen} from '../../product-listing-analytics';
import {pushRecentResult} from '../../product-listing-recent-results';

export interface InteractiveResultOptions extends InteractiveResultCoreOptions {
/**
* The query result.
*/
result: ProductRecommendation;
}

export interface InteractiveResultProps extends InteractiveResultCoreProps {
/**
* The options for the `InteractiveResult` controller.
* */
options: InteractiveResultOptions;
}

/**
* The `InteractiveResult` controller provides an interface for triggering desirable side effects, such as logging UA events to the Coveo Platform, when a user selects a query result.
*/
export interface InteractiveResult extends InteractiveResultCore {}

/**
* Creates an `InteractiveResult` controller instance.
*
* @param engine - The headless engine.
* @param props - The configurable `InteractiveResult` properties.
* @returns An `InteractiveResult` controller instance.
*/
export function buildInteractiveResult(
engine: CommerceEngine,
props: InteractiveResultProps
): InteractiveResult {
let wasOpened = false;

const logAnalyticsIfNeverOpened = () => {
if (wasOpened) {
return;
}
wasOpened = true;
engine.dispatch(logProductRecommendationOpen(props.options.result));
};

const action = () => {
logAnalyticsIfNeverOpened();
engine.dispatch(pushRecentResult(props.options.result));
};

return buildInteractiveResultCore(engine, props, action);
}
20 changes: 20 additions & 0 deletions packages/headless/src/test/mock-engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import thunk from 'redux-thunk';
import {SearchAPIClient} from '../api/search/search-api-client';
import {InsightAPIClient} from '../api/service/insight/insight-api-client';
import {analyticsMiddleware} from '../app/analytics-middleware';
import {CommerceEngine} from '../app/commerce-engine/commerce-engine';
import {CoreEngine} from '../app/engine';
import {InsightEngine} from '../app/insight-engine/insight-engine';
import {InsightThunkExtraArguments} from '../app/insight-thunk-extra-arguments';
Expand All @@ -25,6 +26,7 @@ import {SearchEngine} from '../app/search-engine/search-engine';
import {SearchThunkExtraArguments} from '../app/search-thunk-extra-arguments';
import {CaseAssistEngine} from '../case-assist.index';
import {CaseAssistAppState} from '../state/case-assist-app-state';
import {ProductListingV2AppState} from '../state/commerce-app-state';
import {InsightAppState} from '../state/insight-app-state';
import {ProductListingAppState} from '../state/product-listing-app-state';
import {ProductRecommendationsAppState} from '../state/product-recommendations-app-state';
Expand All @@ -35,6 +37,7 @@ import {buildMockCaseAssistState} from './mock-case-assist-state';
import {buildMockInsightAPIClient} from './mock-insight-api-client';
import {buildMockInsightState} from './mock-insight-state';
import {buildMockProductListingState} from './mock-product-listing-state';
import {buildMockProductListingV2State} from './mock-product-listing-v2-state';
import {buildMockProductRecommendationsState} from './mock-product-recommendations-state';
import {createMockRecommendationState} from './mock-recommendation-state';
import {buildMockSearchAPIClient} from './mock-search-api-client';
Expand All @@ -55,6 +58,7 @@ type AppState =
| RecommendationAppState
| ProductRecommendationsAppState
| ProductListingAppState
| ProductListingV2AppState
| CaseAssistAppState
| InsightAppState;

Expand Down Expand Up @@ -142,6 +146,22 @@ export function buildMockProductListingEngine(
return buildMockCoreEngine(config, buildMockProductListingState);
}

export interface MockCommerceEngine
extends CommerceEngine<ProductListingV2AppState>,
MockEngine {}

/**
* For internal use only.
*
* Returns a non-functional `CommerceEngine`.
* To be used only for unit testing controllers, not for functional tests.
*/
export function buildMockCommerceEngine(
config: Partial<CommerceEngine<ProductListingV2AppState>> = {}
): MockCommerceEngine {
return buildMockCoreEngine(config, buildMockProductListingV2State);
}

export interface MockCaseAssistEngine
extends CaseAssistEngine<CaseAssistAppState>,
MockEngine {}
Expand Down

0 comments on commit a24804a

Please sign in to comment.