Skip to content

Commit

Permalink
fix(quantic): property validation added with dependent facets (#4697)
Browse files Browse the repository at this point in the history
## [SFINT-5821](https://coveord.atlassian.net/browse/SFINT-5821)

When an invalid value is passed to the dependsOn property when using
dependent facets, we are now trigger an error to notify the user about
the mis configuration:

for example, when the following value is passed:

```javascript
  exampleInvalidDependency = {
    expectedValue: '2',
  };

```

The user get this:

<img width="1728" alt="Screenshot 2024-11-21 at 10 52 56 AM"
src="https://github.com/user-attachments/assets/b1eabb73-c882-4f9b-902e-c5232753d689">


[SFINT-5821]:
https://coveord.atlassian.net/browse/SFINT-5821?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
  • Loading branch information
mmitiche authored Nov 25, 2024
1 parent 22d2ff2 commit 110d834
Show file tree
Hide file tree
Showing 10 changed files with 644 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,21 @@ jest.mock('c/quanticUtils', () => ({
CATEGORYFACETS: 'categoryFacets',
},
},
I18nUtils: {
format: jest.fn(),
},
}));
jest.mock('c/quanticHeadlessLoader');

const selectors = {
facetContent: '[data-test="facet-content"]',
componentError: 'c-quantic-component-error',
};

const exampleFacetId = 'example facet id';
const exampleField = 'exampleField';
const defaultOptions = {
field: 'example field',
field: exampleField,
};
const categoryFacetControllerMock = {
subscribe: jest.fn((callback) => callback()),
Expand All @@ -33,6 +38,8 @@ const categoryFacetControllerMock = {
values: [],
},
};
const parentFacetIdError = `The ${exampleField} c-quantic-category-facet requires dependsOn.parentFacetId to be a valid string.`;
const expectedValueError = `The ${exampleField} c-quantic-category-facet requires dependsOn.expectedValue to be a valid string.`;

function createTestComponent(options = defaultOptions) {
prepareHeadlessState();
Expand Down Expand Up @@ -82,6 +89,21 @@ const exampleEngine = {
};
let isInitialized = false;

function mockBueno() {
// @ts-ignore
mockHeadlessLoader.getBueno = () => {
// @ts-ignore
global.Bueno = {
isString: jest
.fn()
.mockImplementation(
(value) => Object.prototype.toString.call(value) === '[object String]'
),
};
return new Promise((resolve) => resolve());
};
}

function mockSuccessfulHeadlessInitialization() {
// @ts-ignore
mockHeadlessLoader.initializeWithHeadless = (element, _, initialize) => {
Expand All @@ -104,6 +126,7 @@ function cleanup() {
describe('c-quantic-category-facet', () => {
beforeAll(() => {
mockSuccessfulHeadlessInitialization();
mockBueno();
});

afterEach(() => {
Expand Down Expand Up @@ -198,6 +221,90 @@ describe('c-quantic-category-facet', () => {
});
});

describe('validation of the dependsOn property', () => {
let consoleError;
beforeAll(() => {
consoleError = jest.spyOn(console, 'error').mockImplementation(() => {});
});

describe('when dependsOn.parentFacetId is not provided', () => {
it('should display the error component', async () => {
const invalidFacetDependency = {
expectedValue: 'txt',
};
const element = createTestComponent({
...defaultOptions,
dependsOn: invalidFacetDependency,
});
await flushPromises();

const componentError = element.shadowRoot.querySelector(
selectors.componentError
);
const facetContent = element.shadowRoot.querySelector(
selectors.facetContent
);

expect(consoleError).toHaveBeenCalledTimes(1);
expect(consoleError).toHaveBeenCalledWith(parentFacetIdError);
expect(componentError).not.toBeNull();
expect(facetContent).toBeNull();
});
});

describe('when dependsOn.parentFacetId is not a string', () => {
it('should display the error component', async () => {
const invalidFacetDependency = {
parentFacetId: 1,
expectedValue: 'txt',
};
const element = createTestComponent({
...defaultOptions,
dependsOn: invalidFacetDependency,
});
await flushPromises();

const componentError = element.shadowRoot.querySelector(
selectors.componentError
);
const facetContent = element.shadowRoot.querySelector(
selectors.facetContent
);

expect(consoleError).toHaveBeenCalledTimes(1);
expect(consoleError).toHaveBeenCalledWith(parentFacetIdError);
expect(componentError).not.toBeNull();
expect(facetContent).toBeNull();
});
});

describe('when dependsOn.expectedValue is not a string', () => {
it('should display the error component', async () => {
const invalidFacetDependency = {
parentFacetId: 'filetype',
expectedValue: 2,
};
const element = createTestComponent({
...defaultOptions,
dependsOn: invalidFacetDependency,
});
await flushPromises();

const componentError = element.shadowRoot.querySelector(
selectors.componentError
);
const facetContent = element.shadowRoot.querySelector(
selectors.facetContent
);

expect(consoleError).toHaveBeenCalledTimes(1);
expect(consoleError).toHaveBeenCalledWith(expectedValueError);
expect(componentError).not.toBeNull();
expect(facetContent).toBeNull();
});
});
});

describe('when the component is disconnected', () => {
it('should make the condition manager stop watching the facet', async () => {
const exampleFacetDependency = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
initializeWithHeadless,
registerToStore,
getHeadlessBundle,
getBueno,
} from 'c/quanticHeadlessLoader';
import {
I18nUtils,
Expand Down Expand Up @@ -301,6 +302,7 @@ export default class QuanticCategoryFacet extends LightningElement {
},
});
if (this.dependsOn) {
this.validateDependsOnProperty();
this.initFacetConditionManager(engine);
}
this.unsubscribe = this.facet.subscribe(() => this.updateState());
Expand Down Expand Up @@ -330,6 +332,26 @@ export default class QuanticCategoryFacet extends LightningElement {
this.dispatchEvent(renderFacetEvent);
}
validateDependsOnProperty() {
if (this.dependsOn) {
getBueno(this).then(() => {
const {parentFacetId, expectedValue} = this.dependsOn;
if (!Bueno.isString(parentFacetId)) {
console.error(
`The ${this.field} ${this.template.host.localName} requires dependsOn.parentFacetId to be a valid string.`
);
this.setInitializationError();
}
if (expectedValue && !Bueno.isString(expectedValue)) {
console.error(
`The ${this.field} ${this.template.host.localName} requires dependsOn.expectedValue to be a valid string.`
);
this.setInitializationError();
}
});
}
}
initFacetConditionManager(engine) {
this.categoryFacetConditionsManager =
this.headless.buildFacetConditionsManager(engine, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,21 @@ jest.mock('c/quanticUtils', () => ({
DATEFACETS: 'dateFacets',
},
},
I18nUtils: {
format: jest.fn(),
},
}));
jest.mock('c/quanticHeadlessLoader');

const selectors = {
facetContent: '[data-test="facet-content"]',
componentError: 'c-quantic-component-error',
};

const exampleFacetId = 'example facet id';
const exampleField = 'exampleField';
const defaultOptions = {
field: 'example field',
field: exampleField,
};
const dateFacetControllerMock = {
subscribe: jest.fn((callback) => callback()),
Expand All @@ -30,6 +35,8 @@ const dateFacetControllerMock = {
values: [],
},
};
const parentFacetIdError = `The ${exampleField} c-quantic-date-facet requires dependsOn.parentFacetId to be a valid string.`;
const expectedValueError = `The ${exampleField} c-quantic-date-facet requires dependsOn.expectedValue to be a valid string.`;

function createTestComponent(options = defaultOptions) {
prepareHeadlessState();
Expand Down Expand Up @@ -79,6 +86,21 @@ const exampleEngine = {
};
let isInitialized = false;

function mockBueno() {
// @ts-ignore
mockHeadlessLoader.getBueno = () => {
// @ts-ignore
global.Bueno = {
isString: jest
.fn()
.mockImplementation(
(value) => Object.prototype.toString.call(value) === '[object String]'
),
};
return new Promise((resolve) => resolve());
};
}

function mockSuccessfulHeadlessInitialization() {
// @ts-ignore
mockHeadlessLoader.initializeWithHeadless = (element, _, initialize) => {
Expand All @@ -101,6 +123,7 @@ function cleanup() {
describe('c-quantic-date-facet', () => {
beforeAll(() => {
mockSuccessfulHeadlessInitialization();
mockBueno();
});

afterEach(() => {
Expand Down Expand Up @@ -193,6 +216,89 @@ describe('c-quantic-date-facet', () => {
});
});

describe('validation of the dependsOn property', () => {
let consoleError;
beforeAll(() => {
consoleError = jest.spyOn(console, 'error').mockImplementation(() => {});
});
describe('when dependsOn.parentFacetId is not provided', () => {
it('should display the error component', async () => {
const invalidFacetDependency = {
expectedValue: 'txt',
};
const element = createTestComponent({
...defaultOptions,
dependsOn: invalidFacetDependency,
});
await flushPromises();

const componentError = element.shadowRoot.querySelector(
selectors.componentError
);
const facetContent = element.shadowRoot.querySelector(
selectors.facetContent
);

expect(consoleError).toHaveBeenCalledTimes(1);
expect(consoleError).toHaveBeenCalledWith(parentFacetIdError);
expect(componentError).not.toBeNull();
expect(facetContent).toBeNull();
});
});

describe('when dependsOn.parentFacetId is not a string', () => {
it('should display the error component', async () => {
const invalidFacetDependency = {
parentFacetId: 1,
expectedValue: 'txt',
};
const element = createTestComponent({
...defaultOptions,
dependsOn: invalidFacetDependency,
});
await flushPromises();

const componentError = element.shadowRoot.querySelector(
selectors.componentError
);
const facetContent = element.shadowRoot.querySelector(
selectors.facetContent
);

expect(consoleError).toHaveBeenCalledTimes(1);
expect(consoleError).toHaveBeenCalledWith(parentFacetIdError);
expect(componentError).not.toBeNull();
expect(facetContent).toBeNull();
});
});

describe('when dependsOn.expectedValue is not a string', () => {
it('should display the error component', async () => {
const invalidFacetDependency = {
parentFacetId: 'filetype',
expectedValue: 2,
};
const element = createTestComponent({
...defaultOptions,
dependsOn: invalidFacetDependency,
});
await flushPromises();

const componentError = element.shadowRoot.querySelector(
selectors.componentError
);
const facetContent = element.shadowRoot.querySelector(
selectors.facetContent
);

expect(consoleError).toHaveBeenCalledTimes(1);
expect(consoleError).toHaveBeenCalledWith(expectedValueError);
expect(componentError).not.toBeNull();
expect(facetContent).toBeNull();
});
});
});

describe('when the component is disconnected', () => {
it('should make the condition manager stop watching the facet', async () => {
const exampleFacetDependency = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
initializeWithHeadless,
registerToStore,
getHeadlessBundle,
getBueno,
} from 'c/quanticHeadlessLoader';
import {
I18nUtils,
Expand Down Expand Up @@ -210,6 +211,7 @@ export default class QuanticDateFacet extends LightningElement {
element: this.template.host,
});
if (this.dependsOn) {
this.validateDependsOnProperty();
this.initFacetConditionManager(engine);
}
};
Expand Down Expand Up @@ -238,6 +240,26 @@ export default class QuanticDateFacet extends LightningElement {
this.dispatchEvent(renderFacetEvent);
}

validateDependsOnProperty() {
if (this.dependsOn) {
getBueno(this).then(() => {
const {parentFacetId, expectedValue} = this.dependsOn;
if (!Bueno.isString(parentFacetId)) {
console.error(
`The ${this.field} ${this.template.host.localName} requires dependsOn.parentFacetId to be a valid string.`
);
this.setInitializationError();
}
if (expectedValue && !Bueno.isString(expectedValue)) {
console.error(
`The ${this.field} ${this.template.host.localName} requires dependsOn.expectedValue to be a valid string.`
);
this.setInitializationError();
}
});
}
}

initFacetConditionManager(engine) {
this.dateFacetConditionsManager = this.headless.buildFacetConditionsManager(
engine,
Expand Down
Loading

0 comments on commit 110d834

Please sign in to comment.