Skip to content

Commit

Permalink
feat(quantic): Exposed the options to be be passed in Did You Mean co…
Browse files Browse the repository at this point in the history
…ntroller in the QuanticDidYouMean component (#4682)

[SFINT-5681](https://coveord.atlassian.net/browse/SFINT-5681)

## IN THIS PR:

- Exposed `disableQueryAutoCorrection` and `queryCorrectionMode`
properties in the `QuanticDidYouMean` component.
- Added unit tests
- Fixed issue with props in the controller of the did-you-mean feature
for insight

Notes:

- This needs to be merge after this
[PR](#4598)
- We are setting `queryCorrectionMode` to legacy to avoid breaking
changes. This will be changed after the next major release, we have the
jira tickets to eventually change that.

## TESTS:

<img width="397" alt="image"
src="https://github.com/user-attachments/assets/a2ae1ee1-a145-465a-a25b-c7623710f2f0">


[SFINT-5681]:
https://coveord.atlassian.net/browse/SFINT-5681?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
  • Loading branch information
SimonMilord authored Nov 21, 2024
1 parent 4700df2 commit 4ccf316
Show file tree
Hide file tree
Showing 3 changed files with 235 additions and 6 deletions.
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 () => {
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

0 comments on commit 4ccf316

Please sign in to comment.