Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(quantic): Exposed the options to be be passed in Did You Mean controller in the QuanticDidYouMean component #4682

Merged
merged 21 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ export type {
DidYouMeanOptions,
};

const defaultDidYouMeanOptions: DidYouMeanOptions = {
automaticallyCorrectQuery: true,
queryCorrectionMode: 'legacy',
};

/**
* The insight DidYouMean controller is responsible for handling query corrections.
* When a query returns no result but finds a possible query correction, the controller either suggests the correction or
Expand All @@ -37,12 +42,15 @@ export type {
export function buildDidYouMean(
engine: InsightEngine,
props: DidYouMeanProps = {
options: {
queryCorrectionMode: 'legacy',
},
options: defaultDidYouMeanOptions,
}
): DidYouMean {
const controller = buildCoreDidYouMean(engine, props);
const options = {
...defaultDidYouMeanOptions,
...props.options,
};

const controller = buildCoreDidYouMean(engine, {options});
const {dispatch} = engine;

return {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
/* eslint-disable no-import-assign */
// @ts-ignore
import {createElement} from 'lwc';
import QuanticDidYouMean from '../quanticDidYouMean';
import * as mockHeadlessLoader from 'c/quanticHeadlessLoader';

jest.mock('c/quanticHeadlessLoader');

let isInitialized = false;

const exampleEngine = {
id: 'exampleEngineId',
};

const defaultQueryCorrectionMode = 'legacy';

const defaultOptions = {
engineId: exampleEngine.id,
disableQueryAutoCorrection: false,
queryCorrectionMode: defaultQueryCorrectionMode,
};

const mockDidYouMeanState = {
wasCorrectedTo: 'example corrected query',
originalQuery: 'example original query',
wasAutomaticallyCorrected: true,
queryCorrection: {
correctedQuery: 'example corrected query',
wordCorrected: 'example',
},
hasQueryCorrection: true,
};

const functionsMocks = {
buildDidYouMean: jest.fn(() => ({
state: mockDidYouMeanState,
subscribe: functionsMocks.subscribe,
})),
buildQueryTrigger: jest.fn(() => ({
state: {},
subscribe: functionsMocks.subscribe,
})),
applyCorrection: jest.fn(() => {}),
subscribe: jest.fn((cb) => {
cb();
return functionsMocks.unsubscribe;
}),
unsubscribe: jest.fn(() => {}),
};

function prepareHeadlessState() {
// @ts-ignore
mockHeadlessLoader.getHeadlessBundle = () => {
return {
buildDidYouMean: functionsMocks.buildDidYouMean,
buildQueryTrigger: functionsMocks.buildQueryTrigger,
};
};
}

function mockSuccessfulHeadlessInitialization() {
// @ts-ignore
mockHeadlessLoader.initializeWithHeadless = (element, _, initialize) => {
if (element instanceof QuanticDidYouMean && !isInitialized) {
isInitialized = true;
initialize(exampleEngine);
}
};
}

function createTestComponent(options = defaultOptions) {
prepareHeadlessState();
const element = createElement('c-quantic-did-you-mean', {
is: QuanticDidYouMean,
});

for (const [key, value] of Object.entries(options)) {
element[key] = value;
}

document.body.appendChild(element);
return element;
}

// Helper function to wait until the microtask queue is empty.
async function flushPromises() {
return Promise.resolve();
}

describe('c-quantic-did-you-mean', () => {
function cleanup() {
// The jsdom instance is shared across test cases in a single file so reset the DOM
while (document.body.firstChild) {
document.body.removeChild(document.body.firstChild);
}
jest.clearAllMocks();
isInitialized = false;
}

beforeEach(() => {
mockSuccessfulHeadlessInitialization();
});

afterEach(() => {
cleanup();
});

describe('controller initialization', () => {
it('should subscribe to the headless state changes', async () => {
SimonMilord marked this conversation as resolved.
Show resolved Hide resolved
createTestComponent();
await flushPromises();

expect(functionsMocks.buildDidYouMean).toHaveBeenCalled();
expect(functionsMocks.subscribe).toHaveBeenCalled();
});

describe('#disableQueryAutoCorrection property', () => {
describe('when disableQueryAutoCorrection is false (default)', () => {
it('should properly initialize the controller with automatic query correction enabled', async () => {
createTestComponent();
await flushPromises();

expect(functionsMocks.buildDidYouMean).toHaveBeenCalledTimes(1);
expect(functionsMocks.buildDidYouMean).toHaveBeenCalledWith(
exampleEngine,
expect.objectContaining({
options: expect.objectContaining({
automaticallyCorrectQuery: true,
queryCorrectionMode: defaultQueryCorrectionMode,
}),
})
);
});
});

describe('when disableQueryAutoCorrection is true', () => {
it('should properly initialize the controller with automatic query correction disabled', async () => {
createTestComponent({
...defaultOptions,
disableQueryAutoCorrection: true,
});
await flushPromises();

expect(functionsMocks.buildDidYouMean).toHaveBeenCalledTimes(1);
expect(functionsMocks.buildDidYouMean).toHaveBeenCalledWith(
exampleEngine,
expect.objectContaining({
options: expect.objectContaining({
automaticallyCorrectQuery: false,
queryCorrectionMode: defaultQueryCorrectionMode,
}),
})
);
});
});
});

describe('#queryCorrectionMode property', () => {
describe('when queryCorrectionMode is `legacy` (default)', () => {
it('should properly initialize the controller with the query correction mode set to `legacy`', async () => {
createTestComponent();
await flushPromises();

expect(functionsMocks.buildDidYouMean).toHaveBeenCalledTimes(1);
expect(functionsMocks.buildDidYouMean).toHaveBeenCalledWith(
exampleEngine,
expect.objectContaining({
options: expect.objectContaining({
automaticallyCorrectQuery: true,
queryCorrectionMode: defaultQueryCorrectionMode,
}),
})
);
});
});

describe('when queryCorrectionMode is `next`', () => {
it('should properly initialize the controller with the query correction mode set to `next`', async () => {
createTestComponent({
...defaultOptions,
queryCorrectionMode: 'next',
});
await flushPromises();

expect(functionsMocks.buildDidYouMean).toHaveBeenCalledTimes(1);
expect(functionsMocks.buildDidYouMean).toHaveBeenCalledWith(
exampleEngine,
expect.objectContaining({
options: expect.objectContaining({
automaticallyCorrectQuery: true,
queryCorrectionMode: 'next',
}),
})
);
});
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import queryTriggerTemplate from './templates/queryTrigger.html';
* @category Search
* @category Insight Panel
* @example
* <c-quantic-did-you-mean engine-id={engineId}></c-quantic-did-you-mean>
* <c-quantic-did-you-mean engine-id={engineId} disable-query-auto-correction={disableQueryAutoCorrection} query-correction-mode="next"></c-quantic-did-you-mean>
*/
export default class QuanticDidYouMean extends LightningElement {
/**
Expand All @@ -38,6 +38,25 @@ export default class QuanticDidYouMean extends LightningElement {
* @type {string}
*/
@api engineId;
/**
* Whether to disable automatically applying corrections for queries that would otherwise return no results.
* When `disableQueryAutoCorrection` is `false`, the component automatically triggers a new query using the suggested term.
* When `disableQueryAutoCorrection` is `true`, the component returns the suggested term without triggering a new query.
* @api
* @type {boolean}
* @defaultValue false
*/
@api disableQueryAutoCorrection = false;
/**
* Defines which query correction system to use.
* `legacy`: Query correction is powered by the legacy index system. This system relies on an algorithm using solely the index content to compute the suggested terms.
* `next`: Query correction is powered by a machine learning system, requiring a valid query suggestion model configured in your Coveo environment to function properly. This system relies on machine learning algorithms to compute the suggested terms.
* The `legacy` system will send two requests to the API to get the suggestions, while the `next` system will send one request.
* @api
* @type {string}
* @defaultValue 'legacy'
*/
@api queryCorrectionMode = 'legacy';

/** @type {boolean}*/
@track hasQueryCorrection;
Expand Down Expand Up @@ -85,7 +104,10 @@ export default class QuanticDidYouMean extends LightningElement {
initialize = (engine) => {
this.headless = getHeadlessBundle(this.engineId);
this.didYouMean = this.headless.buildDidYouMean(engine, {
options: {queryCorrectionMode: 'legacy'},
options: {
queryCorrectionMode: this.queryCorrectionMode,
automaticallyCorrectQuery: !this.disableQueryAutoCorrection,
},
});
this.queryTrigger = this.headless?.buildQueryTrigger?.(engine);
this.unsubscribeDidYouMean = this.didYouMean.subscribe(() =>
Expand Down
Loading