Repository: coveo/ui-kit
Pull request #5183: test(quantic): query summary tests
Author: login: mikegron
Created: 2025-04-16T18:31:22Z | Merged: 2025-04-24T21:47:02Z

https://coveord.atlassian.net/browse/SFINT-6011

Replacing Quantic Summary Cypress tests for jest tests!

![image](https://github.com/user-attachments/assets/bfc2833d-d1fb-4b1a-bd65-6578ab92e1df)
![image](https://github.com/user-attachments/assets/32f5e024-df12-401a-9a6d-302950861c4a)



Diff:
diff --git a/packages/quantic/force-app/main/default/lwc/quanticSummary/__tests__/quanticSummary.test.js b/packages/quantic/force-app/main/default/lwc/quanticSummary/__tests__/quanticSummary.test.js
new file mode 100644
index 00000000000..53b2fa68ae0
--- /dev/null
+++ b/packages/quantic/force-app/main/default/lwc/quanticSummary/__tests__/quanticSummary.test.js
@@ -0,0 +1,244 @@
+jest.mock('c/quanticHeadlessLoader');
+jest.mock('c/quanticUtils');
+
+import QuanticSummary from 'c/quanticSummary';
+import {buildCreateTestComponent, cleanup, flushPromises} from 'c/testUtils';
+import * as quanticHeadlessLoader from 'c/quanticHeadlessLoader';
+import * as quanticUtils from 'c/quanticUtils';
+
+const headlessLoaderMock = jest.mocked(quanticHeadlessLoader);
+const engineMock = {
+  id: 'mockEngine',
+  dispatch: jest.fn(),
+};
+
+const functionsMocks = {
+  subscribe: jest.fn((cb) => {
+    cb();
+    return functionsMocks.unsubscribe;
+  }),
+  unsubscribe: jest.fn(() => {}),
+};
+
+const summaryControllerMock = {
+  subscribe: functionsMocks.subscribe,
+};
+
+const headlessMock = {
+  buildQuerySummary: jest.fn().mockReturnValue(summaryControllerMock),
+};
+headlessLoaderMock.getHeadlessBundle.mockReturnValue(headlessMock);
+headlessLoaderMock.initializeWithHeadless.mockImplementation(
+  async (element, _, initialize) => {
+    if (element instanceof QuanticSummary) {
+      initialize(engineMock);
+    }
+  }
+);
+
+const quanticUtilsMock = jest.mocked(quanticUtils);
+quanticUtilsMock.AriaLiveRegion.mockReturnValue({
+  dispatchMessage: jest.fn(),
+  registerRegion: undefined,
+});
+quanticUtilsMock.I18nUtils.escapeHTML.mockImplementation((str) => str);
+quanticUtilsMock.I18nUtils.getTextWithDecorator.mockImplementation(
+  (str) => str
+);
+quanticUtilsMock.I18nUtils.format.mockImplementation((str, ...args) =>
+  args.join(' ')
+);
+
+const selectors = {
+  summary: 'lightning-formatted-rich-text',
+  componentError: 'c-quantic-component-error',
+};
+
+const createTestComponent = buildCreateTestComponent(
+  QuanticSummary,
+  'c-quantic-summary',
+  {engineId: engineMock.id}
+);
+
+describe('c-quantic-summary', () => {
+  beforeEach(() => {
+    summaryControllerMock.state = {
+      hasResults: true,
+      hasQuery: false,
+      firstResult: 1,
+      lastResult: 10,
+      total: 100,
+      query: '',
+    };
+  });
+
+  afterEach(() => {
+    cleanup();
+  });
+
+  it('should build headless controller and subscribe on initialization', async () => {
+    createTestComponent();
+    await flushPromises();
+
+    expect(headlessLoaderMock.getHeadlessBundle).toHaveBeenCalledWith(
+      engineMock.id
+    );
+    expect(headlessLoaderMock.initializeWithHeadless).toHaveBeenCalledWith(
+      expect.any(QuanticSummary),
+      engineMock.id,
+      expect.any(Function)
+    );
+    expect(headlessMock.buildQuerySummary).toHaveBeenCalledWith(engineMock);
+    expect(summaryControllerMock.subscribe).toHaveBeenCalledWith(
+      expect.any(Function)
+    );
+  });
+
+  it('should not render before results have returned', () => {
+    summaryControllerMock.state.hasResults = false;
+
+    const element = createTestComponent();
+
+    const summary = element.shadowRoot.querySelector(selectors.summary);
+    expect(summary).toBeNull();
+  });
+
+  describe('when loading default summary', () => {
+    it('should render the summary correctly', async () => {
+      const element = createTestComponent();
+      await flushPromises();
+
+      const summary = element.shadowRoot.querySelector(selectors.summary);
+      expect(summary).not.toBeNull();
+      expect(summary.value).toBe(
+        `${summaryControllerMock.state.firstResult}-${summaryControllerMock.state.lastResult} ${summaryControllerMock.state.total} `
+      );
+      expect(
+        quanticUtilsMock.I18nUtils.getTextWithDecorator
+      ).toHaveBeenCalledWith(
+        `${summaryControllerMock.state.total}`,
+        '<b class="summary__total">',
+        '</b>'
+      );
+      expect(
+        quanticUtilsMock.I18nUtils.getTextWithDecorator
+      ).toHaveBeenCalledWith(
+        `${summaryControllerMock.state.firstResult}-${summaryControllerMock.state.lastResult}`,
+        '<b class="summary__range">',
+        '</b>'
+      );
+      expect(
+        quanticUtilsMock.I18nUtils.getLabelNameWithCount
+      ).toHaveBeenCalledWith(
+        'showingResultsOf',
+        summaryControllerMock.state.lastResult
+      );
+    });
+  });
+
+  describe('when there is an initialization error', () => {
+    it('should show the error component', async () => {
+      headlessLoaderMock.initializeWithHeadless.mockImplementationOnce(
+        async (element) => {
+          if (element instanceof QuanticSummary) {
+            element.setInitializationError();
+          }
+        }
+      );
+
+      const element = createTestComponent();
+      await flushPromises();
+
+      const error = element.shadowRoot.querySelector(selectors.componentError);
+      expect(error).toBeTruthy();
+    });
+
+    describe('when a query yields no results', () => {
+      it('should not render anything', async () => {
+        summaryControllerMock.state.hasResults = false;
+        const element = createTestComponent();
+        await flushPromises();
+
+        const summary = element.shadowRoot.querySelector(selectors.summary);
+        expect(summary).toBeNull();
+      });
+    });
+
+    describe('when the page changes', () => {
+      it('should display the correct per-page number', async () => {
+        summaryControllerMock.state.firstResult = 11;
+        summaryControllerMock.state.lastResult = 20;
+        summaryControllerMock.state.total = 200;
+
+        createTestComponent();
+        await flushPromises();
+        expect(
+          quanticUtilsMock.I18nUtils.getTextWithDecorator
+        ).toHaveBeenCalledWith(
+          `${summaryControllerMock.state.total}`,
+          '<b class="summary__total">',
+          '</b>'
+        );
+        expect(
+          quanticUtilsMock.I18nUtils.getTextWithDecorator
+        ).toHaveBeenCalledWith(
+          `${summaryControllerMock.state.firstResult}-${summaryControllerMock.state.lastResult}`,
+          '<b class="summary__range">',
+          '</b>'
+        );
+        expect(
+          quanticUtilsMock.I18nUtils.getLabelNameWithCount
+        ).toHaveBeenCalledWith(
+          'showingResultsOf',
+          summaryControllerMock.state.lastResult
+        );
+      });
+    });
+
+    describe('when a query is entered', () => {
+      it('should display the query in the summary', async () => {
+        summaryControllerMock.state.query =
+          'Makita 18V LXT brushless impact driver XDT14Z';
+        summaryControllerMock.state.hasQuery = true;
+        const element = createTestComponent();
+        await flushPromises();
+
+        const summary = element.shadowRoot.querySelector(selectors.summary);
+        expect(summary).not.toBeNull();
+        expect(
+          quanticUtilsMock.I18nUtils.getTextWithDecorator
+        ).toHaveBeenCalledWith(
+          `${summaryControllerMock.state.total}`,
+          '<b class="summary__total">',
+          '</b>'
+        );
+        expect(
+          quanticUtilsMock.I18nUtils.getTextWithDecorator
+        ).toHaveBeenCalledWith(
+          `${summaryControllerMock.state.firstResult}-${summaryControllerMock.state.lastResult}`,
+          '<b class="summary__range">',
+          '</b>'
+        );
+        expect(quanticUtilsMock.I18nUtils.escapeHTML).toHaveBeenCalledWith(
+          summaryControllerMock.state.query
+        );
+        expect(
+          quanticUtilsMock.I18nUtils.getTextWithDecorator
+        ).toHaveBeenCalledWith(
+          summaryControllerMock.state.query,
+          '<b class="summary__query">',
+          '</b>'
+        );
+        expect(
+          quanticUtilsMock.I18nUtils.getLabelNameWithCount
+        ).toHaveBeenCalledWith(
+          'showingResultsOfWithQuery',
+          summaryControllerMock.state.lastResult
+        );
+        expect(summary.value).toBe(
+          `${summaryControllerMock.state.firstResult}-${summaryControllerMock.state.lastResult} ${summaryControllerMock.state.total} ${summaryControllerMock.state.query}`
+        );
+      });
+    });
+  });
+});
diff --git a/packages/quantic/playwright/page-object/searchObject.ts b/packages/quantic/playwright/page-object/searchObject.ts
index e8a00ec706b..316a9cbfb9f 100644
--- a/packages/quantic/playwright/page-object/searchObject.ts
+++ b/packages/quantic/playwright/page-object/searchObject.ts
@@ -80,6 +80,24 @@ export class SearchObject {
     });
   }
 
+  async mockEmptySearchResponse() {
+    await this.page.route(this.searchRequestRegex, async (route) => {
+      const apiResponse = await this.page.request.fetch(route.request());
+      const originalBody = await apiResponse.json();
+      originalBody.results = [];
+      originalBody.totalCount = 0;
+      originalBody.totalCountFiltered = 0;
+
+      await route.fulfill({
+        body: JSON.stringify(originalBody),
+        status: 200,
+        headers: {
+          'content-type': 'application/json',
+        },
+      });
+    });
+  }
+
   extractDataFromResponse(response: Response) {
     return response.request().postDataJSON();
   }


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: 

Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.




state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T18:21:51Z
user: login: SimonMilord
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-18T15:58:09Z
user: login: SimonMilord
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-18T16:02:40Z
user: login: SimonMilord
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-18T16:03:17Z
user: login: SimonMilord
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-18T16:04:02Z
user: login: SimonMilord
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-18T16:04:46Z
user: login: SimonMilord
body: I think you should add a test for the analytics sent. For example, when we change the results per page, it should send a `pagerResize` event :
<img width="1388" alt="image" src="https://github.com/user-attachments/assets/a1605902-ddf5-4763-9040-ed5675b473a9" />

state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-18T16:10:33Z
user: login: mikegron
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T14:06:08Z
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This pull request replaces the existing Cypress tests for the Quantic Summary component with Jest tests. It includes the addition of new Jest test cases, updated page objects, and fixtures to support testing the summary behavior.

### Reviewed Changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.

<details>
<summary>Show a summary per file</summary>

| File | Description |
| ---- | ----------- |
| packages/quantic/playwright/page-object/searchObject.ts | Adds a helper method to mock an empty search response via Playwright. |
| packages/quantic/force-app/main/default/lwc/quanticSummary/e2e/quanticSummary.e2e.ts | Implements end-to-end test cases for the summary component covering different user interactions. |
| packages/quantic/force-app/main/default/lwc/quanticSummary/e2e/pageObject.ts | Provides a page object for interacting with the summary component in E2E tests. |
| packages/quantic/force-app/main/default/lwc/quanticSummary/e2e/fixture.ts | Configures test fixtures for both search and insight scenarios using the new Jest framework. |
| packages/quantic/force-app/main/default/lwc/quanticSummary/__tests__/quanticSummary.test.js | Contains Jest unit tests verifying component initialization, state updates, and error handling. |
</details>





state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T14:07:30Z
user: login: SimonMilord
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T16:35:30Z
user: login: SimonMilord
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T16:37:07Z
user: login: erocheleau
body: I think this is an example of a component where we don't need e2e tests at all really.

Also please make a pass so the unit test are more in line with the format of all the other unit tests, it makes it a lot easier to find stuff and also review :)
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-23T22:30:46Z
user: login: mikegron
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T15:03:03Z

Review comments:
diff_hunk: @@ -0,0 +1,124 @@
+jest.mock('c/quanticHeadlessLoader');
+import QuanticSummary from 'c/quanticSummary';
+import {buildCreateTestComponent, cleanup, flushPromises} from 'c/testUtils';
+import * as quanticHeadlessLoader from 'c/quanticHeadlessLoader';
+
+const headlessLoaderMock = jest.mocked(quanticHeadlessLoader);
+const engineMock = {
+  id: 'mockEngine',
+  dispatch: jest.fn(),
+};
+
+let updateState;
+const summaryControllerMock = {
+  subscribe: jest.fn((listener) => {
+    updateState = async () => {
+      listener();
+      await flushPromises();
+    };
+    return () => {}; // Fake unsubscribe function
path: packages/quantic/force-app/main/default/lwc/quanticSummary/__tests__/quanticSummary.test.js
user: login: SimonMilord
body: just for consistency, we usually mock the unsubscribe like this:
<img width="318" alt="image" src="https://github.com/user-attachments/assets/8a474df5-38d7-4b92-a596-918b162e3e33" />

created_at: 2025-04-18T15:58:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,132 @@
+import {useCaseTestCases} from '../../../../../../playwright/utils/useCase';
+import {testSearch, testInsight, expect} from './fixture';
+
+const fixtures = {
+  search: testSearch as typeof testSearch,
+  insight: testInsight as typeof testInsight,
+};
+
+useCaseTestCases.forEach((useCase) => {
+  let test = fixtures[useCase.value];
+  test.describe(`quantic-summary ${useCase.label}`, () => {
+    test('should not render before results have returned', async ({
path: packages/quantic/force-app/main/default/lwc/quanticSummary/e2e/quanticSummary.e2e.ts
user: login: SimonMilord
body: you can remove this test as you are already testing that in the unit tests
created_at: 2025-04-18T16:02:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,132 @@
+import {useCaseTestCases} from '../../../../../../playwright/utils/useCase';
+import {testSearch, testInsight, expect} from './fixture';
+
+const fixtures = {
+  search: testSearch as typeof testSearch,
+  insight: testInsight as typeof testInsight,
+};
+
+useCaseTestCases.forEach((useCase) => {
+  let test = fixtures[useCase.value];
+  test.describe(`quantic-summary ${useCase.label}`, () => {
+    test('should not render before results have returned', async ({
+      summary,
+      configuration,
+    }) => {
+      await configuration.reset();
+      await configuration.configure({useCase: useCase.value});
+
+      await expect(summary.summary).not.toBeVisible();
+    });
+
+    test.describe('when loading default summary', () => {
+      test('should work as expected and display the summary', async ({
path: packages/quantic/force-app/main/default/lwc/quanticSummary/e2e/quanticSummary.e2e.ts
user: login: SimonMilord
body: you are also testing this in your unit tests.
created_at: 2025-04-18T16:03:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,132 @@
+import {useCaseTestCases} from '../../../../../../playwright/utils/useCase';
+import {testSearch, testInsight, expect} from './fixture';
+
+const fixtures = {
+  search: testSearch as typeof testSearch,
+  insight: testInsight as typeof testInsight,
+};
+
+useCaseTestCases.forEach((useCase) => {
+  let test = fixtures[useCase.value];
+  test.describe(`quantic-summary ${useCase.label}`, () => {
+    test('should not render before results have returned', async ({
+      summary,
+      configuration,
+    }) => {
+      await configuration.reset();
+      await configuration.configure({useCase: useCase.value});
+
+      await expect(summary.summary).not.toBeVisible();
+    });
+
+    test.describe('when loading default summary', () => {
+      test('should work as expected and display the summary', async ({
+        search,
+        summary,
+      }) => {
+        const defaultNumberOfResults = 10;
+        const searchResponsePromise = search.waitForSearchResponse();
+        await search.performSearch();
+        const searchResponse = await searchResponsePromise;
+        const totalCount = (await searchResponse.json()).totalCount;
+
+        await expect(summary.summaryQuery).not.toBeVisible();
+        await expect(summary.summaryRange).toBeVisible();
+        await expect(await summary.getRange()).toBe(
+          `1-${defaultNumberOfResults}`
+        );
+        await expect(summary.summaryTotal).toBeVisible();
+        await expect(await summary.getTotal()).toBe(
+          Intl.NumberFormat().format(totalCount)
+        );
+      });
+    });
+
+    test.describe('when a query yields no results', () => {
+      test('should not render anything', async ({search, summary}) => {
path: packages/quantic/force-app/main/default/lwc/quanticSummary/e2e/quanticSummary.e2e.ts
user: login: SimonMilord
body: this test should be a unit test
created_at: 2025-04-18T16:04:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,132 @@
+import {useCaseTestCases} from '../../../../../../playwright/utils/useCase';
+import {testSearch, testInsight, expect} from './fixture';
+
+const fixtures = {
+  search: testSearch as typeof testSearch,
+  insight: testInsight as typeof testInsight,
+};
+
+useCaseTestCases.forEach((useCase) => {
+  let test = fixtures[useCase.value];
+  test.describe(`quantic-summary ${useCase.label}`, () => {
+    test('should not render before results have returned', async ({
+      summary,
+      configuration,
+    }) => {
+      await configuration.reset();
+      await configuration.configure({useCase: useCase.value});
+
+      await expect(summary.summary).not.toBeVisible();
+    });
+
+    test.describe('when loading default summary', () => {
+      test('should work as expected and display the summary', async ({
+        search,
+        summary,
+      }) => {
+        const defaultNumberOfResults = 10;
+        const searchResponsePromise = search.waitForSearchResponse();
+        await search.performSearch();
+        const searchResponse = await searchResponsePromise;
+        const totalCount = (await searchResponse.json()).totalCount;
+
+        await expect(summary.summaryQuery).not.toBeVisible();
+        await expect(summary.summaryRange).toBeVisible();
+        await expect(await summary.getRange()).toBe(
+          `1-${defaultNumberOfResults}`
+        );
+        await expect(summary.summaryTotal).toBeVisible();
+        await expect(await summary.getTotal()).toBe(
+          Intl.NumberFormat().format(totalCount)
+        );
+      });
+    });
+
+    test.describe('when a query yields no results', () => {
+      test('should not render anything', async ({search, summary}) => {
+        await search.mockEmptySearchResponse();
+        await search.performSearch();
+        await search.waitForSearchResponse();
+
+        await expect(summary.summary).not.toBeVisible();
+      });
+    });
+
+    test.describe('when selecting result per page', () => {
+      test('should display the correct per-page number', async ({
+        search,
+        summary,
+      }) => {
+        const customResultsPerPage = 45;
+        const searchResponsePromise = search.waitForSearchResponse();
+        await summary.setResultsPerPage(customResultsPerPage);
+        const searchResponse = await searchResponsePromise;
+
+        const totalCount = (await searchResponse.json()).totalCount;
+
+        await expect(summary.summaryQuery).not.toBeVisible();
+        await expect(summary.summaryRange).toBeVisible();
+        await expect(await summary.getRange()).toBe(
+          `1-${customResultsPerPage}`
+        );
+        await expect(summary.summaryTotal).toBeVisible();
+        await expect(await summary.getTotal()).toBe(
+          Intl.NumberFormat().format(totalCount)
+        );
+      });
+    });
+
+    test.describe('when selecting next page', () => {
+      test('should update the summary', async ({search, summary}) => {
+        const defaultNumberOfResults = 10;
+        await summary.goToNextPage();
+
+        const searchResponse = await search.waitForSearchResponse();
+        const totalCount = (await searchResponse.json()).totalCount;
+
+        await expect(summary.summaryQuery).not.toBeVisible();
+        await expect(summary.summaryRange).toBeVisible();
+        await expect(await summary.getRange()).toBe(
+          `${defaultNumberOfResults + 1}-${defaultNumberOfResults * 2}`
+        );
+        await expect(summary.summaryTotal).toBeVisible();
+        await expect(await summary.getTotal()).toBe(
+          Intl.NumberFormat().format(totalCount)
+        );
+      });
+    });
+
+    if (useCase.value === 'search') {
+      test.describe('when a query is entered', () => {
+        const query = 'test';
+        test.use({
+          urlHash: 'q=' + query,
+        });
+
+        test('should display the query in the summary', async ({summary}) => {
path: packages/quantic/force-app/main/default/lwc/quanticSummary/e2e/quanticSummary.e2e.ts
user: login: SimonMilord
body: similar comments for the search use case, the rendering tests should be done in the unit tests
created_at: 2025-04-18T16:04:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,124 @@
+jest.mock('c/quanticHeadlessLoader');
+import QuanticSummary from 'c/quanticSummary';
+import {buildCreateTestComponent, cleanup, flushPromises} from 'c/testUtils';
+import * as quanticHeadlessLoader from 'c/quanticHeadlessLoader';
+
+const headlessLoaderMock = jest.mocked(quanticHeadlessLoader);
+const engineMock = {
+  id: 'mockEngine',
+  dispatch: jest.fn(),
+};
+
+let updateState;
+const summaryControllerMock = {
+  subscribe: jest.fn((listener) => {
+    updateState = async () => {
+      listener();
+      await flushPromises();
+    };
+    return () => {}; // Fake unsubscribe function
path: packages/quantic/force-app/main/default/lwc/quanticSummary/__tests__/quanticSummary.test.js
user: login: erocheleau
body: yea please just copy the usual structure :D makes it a lot faster and easier to code and to review also 
created_at: 2025-04-18T21:05:00Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,124 @@
+jest.mock('c/quanticHeadlessLoader');
+import QuanticSummary from 'c/quanticSummary';
+import {buildCreateTestComponent, cleanup, flushPromises} from 'c/testUtils';
+import * as quanticHeadlessLoader from 'c/quanticHeadlessLoader';
+
+const headlessLoaderMock = jest.mocked(quanticHeadlessLoader);
+const engineMock = {
+  id: 'mockEngine',
+  dispatch: jest.fn(),
+};
+
+let updateState;
+const summaryControllerMock = {
+  subscribe: jest.fn((listener) => {
+    updateState = async () => {
+      listener();
+      await flushPromises();
+    };
+    return () => {}; // Fake unsubscribe function
path: packages/quantic/force-app/main/default/lwc/quanticSummary/__tests__/quanticSummary.test.js
user: login: mikegron
body: C'est bizzare messemble, la je save la callback method dans une variable pour la caller a chaque test, mais je ne valide pas vraiment que unsubscribe est callé. Dans ton exemple tu check aussi que c'est callé?

created_at: 2025-04-22T14:06:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,124 @@
+jest.mock('c/quanticHeadlessLoader');
+import QuanticSummary from 'c/quanticSummary';
+import {buildCreateTestComponent, cleanup, flushPromises} from 'c/testUtils';
+import * as quanticHeadlessLoader from 'c/quanticHeadlessLoader';
+
+const headlessLoaderMock = jest.mocked(quanticHeadlessLoader);
+const engineMock = {
+  id: 'mockEngine',
+  dispatch: jest.fn(),
+};
+
+let updateState;
+const summaryControllerMock = {
+  subscribe: jest.fn((listener) => {
+    updateState = async () => {
+      listener();
+      await flushPromises();
+    };
+    return () => {}; // Fake unsubscribe function
path: packages/quantic/force-app/main/default/lwc/quanticSummary/__tests__/quanticSummary.test.js
user: login: SimonMilord
body: yup tu peux search `unsubscriber` et ca va te montrer des examples
created_at: 2025-04-22T16:35:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,68 @@
+import {SummaryObject} from './pageObject';
+import {quanticBase} from '../../../../../../playwright/fixtures/baseFixture';
+import {SearchObject} from '../../../../../../playwright/page-object/searchObject';
+import {InsightSetupObject} from '../../../../../../playwright/page-object/insightSetupObject';
+import {useCaseEnum} from '../../../../../../playwright/utils/useCase';
+import {
+  searchRequestRegex,
+  insightSearchRequestRegex,
+} from '../../../../../../playwright/utils/requests';
+
+const pageUrl = 's/quantic-summary';
+
+interface SummaryOptions {
+  engineId: string;
+}
path: packages/quantic/force-app/main/default/lwc/quanticSummary/e2e/fixture.ts
user: login: erocheleau
body: I don't think you need this.
created_at: 2025-04-23T22:15:03Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,68 @@
+import {SummaryObject} from './pageObject';
+import {quanticBase} from '../../../../../../playwright/fixtures/baseFixture';
+import {SearchObject} from '../../../../../../playwright/page-object/searchObject';
+import {InsightSetupObject} from '../../../../../../playwright/page-object/insightSetupObject';
+import {useCaseEnum} from '../../../../../../playwright/utils/useCase';
+import {
+  searchRequestRegex,
+  insightSearchRequestRegex,
+} from '../../../../../../playwright/utils/requests';
+
+const pageUrl = 's/quantic-summary';
+
+interface SummaryOptions {
+  engineId: string;
+}
+
+type QuanticSummaryE2EBaseFixtures = {
+  summary: SummaryObject;
+  search: SearchObject;
+  options: Partial<SummaryOptions>;
path: packages/quantic/force-app/main/default/lwc/quanticSummary/e2e/fixture.ts
user: login: erocheleau
body: I don't think you need options?
created_at: 2025-04-23T22:15:47Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,68 @@
+import {SummaryObject} from './pageObject';
+import {quanticBase} from '../../../../../../playwright/fixtures/baseFixture';
+import {SearchObject} from '../../../../../../playwright/page-object/searchObject';
+import {InsightSetupObject} from '../../../../../../playwright/page-object/insightSetupObject';
+import {useCaseEnum} from '../../../../../../playwright/utils/useCase';
+import {
+  searchRequestRegex,
+  insightSearchRequestRegex,
+} from '../../../../../../playwright/utils/requests';
+
+const pageUrl = 's/quantic-summary';
+
+interface SummaryOptions {
+  engineId: string;
+}
+
+type QuanticSummaryE2EBaseFixtures = {
+  summary: SummaryObject;
+  search: SearchObject;
+  options: Partial<SummaryOptions>;
+};
+
+type QuanticSummaryE2ESearchFixtures = QuanticSummaryE2EBaseFixtures & {
+  urlHash: string;
path: packages/quantic/force-app/main/default/lwc/quanticSummary/e2e/fixture.ts
user: login: erocheleau
body: You don't need a urlHash AFAIK for this component?
created_at: 2025-04-23T22:17:10Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,75 @@
+import {useCaseTestCases} from '../../../../../../playwright/utils/useCase';
+import {testSearch, testInsight, expect} from './fixture';
+
+const fixtures = {
+  search: testSearch as typeof testSearch,
+  insight: testInsight as typeof testInsight,
+};
+
+useCaseTestCases.forEach((useCase) => {
+  let test = fixtures[useCase.value];
+  test.describe(`quantic-summary ${useCase.label}`, () => {
+    test.describe('when selecting result per page', () => {
+      test('should display the correct per-page number and log analytics', async ({
path: packages/quantic/force-app/main/default/lwc/quanticSummary/e2e/quanticSummary.e2e.ts
user: login: erocheleau
body: That's not really a function of the quanticSummary.

The whole search/analytics part should already be tested in the `quanticResultsPerPage` component.

And what gets displayed in the quanticSummary is really more of a unit test than an e2e test.
created_at: 2025-04-23T22:21:41Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,75 @@
+import {useCaseTestCases} from '../../../../../../playwright/utils/useCase';
+import {testSearch, testInsight, expect} from './fixture';
+
+const fixtures = {
+  search: testSearch as typeof testSearch,
+  insight: testInsight as typeof testInsight,
+};
+
+useCaseTestCases.forEach((useCase) => {
+  let test = fixtures[useCase.value];
+  test.describe(`quantic-summary ${useCase.label}`, () => {
+    test.describe('when selecting result per page', () => {
+      test('should display the correct per-page number and log analytics', async ({
+        search,
+        summary,
+      }) => {
+        const customResultsPerPage = 45;
+        const searchResponsePromise = search.waitForSearchResponse();
+        const analyticsRequestPromise = summary.waitForCustomEvent();
+        await summary.setResultsPerPage(customResultsPerPage);
+        const searchResponse = await searchResponsePromise;
+        const analyticsRequest = await analyticsRequestPromise;
+
+        const totalCount = (await searchResponse.json()).totalCount;
+
+        await expect(summary.summaryQuery).not.toBeVisible();
+        await expect(summary.summaryRange).toBeVisible();
+        await expect(await summary.getRange()).toBe(
+          `1-${customResultsPerPage}`
+        );
+        await expect(summary.summaryTotal).toBeVisible();
+        await expect(await summary.getTotal()).toBe(
+          Intl.NumberFormat().format(totalCount)
+        );
+
+        const analyticsBody = analyticsRequest.postDataJSON();
+        expect(analyticsBody.eventType).toBe('getMoreResults');
+        expect(analyticsBody.eventValue).toBe('pagerResize');
+        expect(analyticsBody.customData.currentResultsPerPage).toBe(
+          customResultsPerPage
+        );
+      });
+    });
+
+    test.describe('when selecting next page', () => {
+      test('should update the summary and log analytics', async ({
path: packages/quantic/force-app/main/default/lwc/quanticSummary/e2e/quanticSummary.e2e.ts
user: login: erocheleau
body: Same with this one, honestly I don't see a lot with the `QuanticSummary` that should be a e2e test.

It is really more of only unit tests.
created_at: 2025-04-23T22:23:57Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,252 @@
+jest.mock('c/quanticHeadlessLoader');
+jest.mock('c/quanticUtils');
+
+import QuanticSummary from 'c/quanticSummary';
+import {buildCreateTestComponent, cleanup, flushPromises} from 'c/testUtils';
+import * as quanticHeadlessLoader from 'c/quanticHeadlessLoader';
+import * as quanticUtils from 'c/quanticUtils';
+
+const headlessLoaderMock = jest.mocked(quanticHeadlessLoader);
+const engineMock = {
+  id: 'mockEngine',
+  dispatch: jest.fn(),
+};
+
+let updateState;
+const summaryControllerMock = {
+  subscribe: jest.fn((listener) => {
+    updateState = async () => {
+      listener();
+      await flushPromises();
+    };
+    return () => {}; // Fake unsubscribe function
+  }),
+};
+
+const headlessMock = {
+  buildQuerySummary: jest.fn().mockReturnValue(summaryControllerMock),
+};
+headlessLoaderMock.getHeadlessBundle.mockReturnValue(headlessMock);
+headlessLoaderMock.initializeWithHeadless.mockImplementation(
+  async (element, _, initialize) => {
+    if (element instanceof QuanticSummary) {
+      initialize(engineMock);
+    }
+  }
+);
+
+const quanticUtilsMock = jest.mocked(quanticUtils);
+quanticUtilsMock.AriaLiveRegion.mockReturnValue({
+  dispatchMessage: jest.fn(),
+  registerRegion: undefined,
+});
+quanticUtilsMock.I18nUtils.escapeHTML.mockImplementation((str) => str);
+quanticUtilsMock.I18nUtils.getTextWithDecorator.mockImplementation(
+  (str) => str
+);
+quanticUtilsMock.I18nUtils.format.mockImplementation((str, ...args) =>
+  args.join(' ')
+);
+
+const selectors = {
+  summary: 'lightning-formatted-rich-text',
+  componentError: 'c-quantic-component-error',
+};
+
+const createTestComponent = buildCreateTestComponent(
+  QuanticSummary,
+  'c-quantic-summary',
+  {engineId: engineMock.id}
+);
+
+describe('c-quantic-summary', () => {
+  beforeEach(() => {
+    summaryControllerMock.state = {
+      hasResults: true,
+      hasQuery: false,
+      firstResult: 1,
+      lastResult: 10,
+      total: 100,
+      query: '',
+    };
+  });
+
+  afterEach(() => {
+    cleanup();
+  });
+
+  it('should build headless controller and subscribe on initialization', async () => {
+    createTestComponent();
+    await flushPromises();
+
+    expect(headlessLoaderMock.getHeadlessBundle).toHaveBeenCalledWith(
+      engineMock.id
+    );
+    expect(headlessLoaderMock.getHeadlessBundle).toHaveBeenCalledTimes(1);
+    expect(headlessLoaderMock.initializeWithHeadless).toHaveBeenCalledWith(
+      expect.any(QuanticSummary),
+      engineMock.id,
+      expect.any(Function)
+    );
+    expect(headlessLoaderMock.initializeWithHeadless).toHaveBeenCalledTimes(1);
+    expect(headlessMock.buildQuerySummary).toHaveBeenCalledWith(engineMock);
+    expect(headlessMock.buildQuerySummary).toHaveBeenCalledTimes(1);
+    expect(summaryControllerMock.subscribe).toHaveBeenCalledWith(
+      expect.any(Function)
+    );
+    expect(summaryControllerMock.subscribe).toHaveBeenCalledTimes(1);
+  });
+
+  it('should not render before results have returned', () => {
+    summaryControllerMock.state.hasResults = false;
+
+    const element = createTestComponent();
+
+    const summary = element.shadowRoot.querySelector(selectors.summary);
+    expect(summary).toBeFalsy();
+  });
+
+  describe('when loading default summary', () => {
+    it('should render the summary correctly', async () => {
+      const element = createTestComponent();
+      await updateState();
+
+      const summary = element.shadowRoot.querySelector(selectors.summary);
+      expect(summary).toBeTruthy();
path: packages/quantic/force-app/main/default/lwc/quanticSummary/__tests__/quanticSummary.test.js
user: login: erocheleau
body: 
```suggestion
      expect(summary).not.toBeNull();
```
created_at: 2025-04-23T22:27:15Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,252 @@
+jest.mock('c/quanticHeadlessLoader');
+jest.mock('c/quanticUtils');
+
+import QuanticSummary from 'c/quanticSummary';
+import {buildCreateTestComponent, cleanup, flushPromises} from 'c/testUtils';
+import * as quanticHeadlessLoader from 'c/quanticHeadlessLoader';
+import * as quanticUtils from 'c/quanticUtils';
+
+const headlessLoaderMock = jest.mocked(quanticHeadlessLoader);
+const engineMock = {
+  id: 'mockEngine',
+  dispatch: jest.fn(),
+};
+
+let updateState;
+const summaryControllerMock = {
+  subscribe: jest.fn((listener) => {
+    updateState = async () => {
+      listener();
+      await flushPromises();
+    };
+    return () => {}; // Fake unsubscribe function
+  }),
+};
+
+const headlessMock = {
+  buildQuerySummary: jest.fn().mockReturnValue(summaryControllerMock),
+};
+headlessLoaderMock.getHeadlessBundle.mockReturnValue(headlessMock);
+headlessLoaderMock.initializeWithHeadless.mockImplementation(
+  async (element, _, initialize) => {
+    if (element instanceof QuanticSummary) {
+      initialize(engineMock);
+    }
+  }
+);
+
+const quanticUtilsMock = jest.mocked(quanticUtils);
+quanticUtilsMock.AriaLiveRegion.mockReturnValue({
+  dispatchMessage: jest.fn(),
+  registerRegion: undefined,
+});
+quanticUtilsMock.I18nUtils.escapeHTML.mockImplementation((str) => str);
+quanticUtilsMock.I18nUtils.getTextWithDecorator.mockImplementation(
+  (str) => str
+);
+quanticUtilsMock.I18nUtils.format.mockImplementation((str, ...args) =>
+  args.join(' ')
+);
+
+const selectors = {
+  summary: 'lightning-formatted-rich-text',
+  componentError: 'c-quantic-component-error',
+};
+
+const createTestComponent = buildCreateTestComponent(
+  QuanticSummary,
+  'c-quantic-summary',
+  {engineId: engineMock.id}
+);
+
+describe('c-quantic-summary', () => {
+  beforeEach(() => {
+    summaryControllerMock.state = {
+      hasResults: true,
+      hasQuery: false,
+      firstResult: 1,
+      lastResult: 10,
+      total: 100,
+      query: '',
+    };
+  });
+
+  afterEach(() => {
+    cleanup();
+  });
+
+  it('should build headless controller and subscribe on initialization', async () => {
+    createTestComponent();
+    await flushPromises();
+
+    expect(headlessLoaderMock.getHeadlessBundle).toHaveBeenCalledWith(
+      engineMock.id
+    );
+    expect(headlessLoaderMock.getHeadlessBundle).toHaveBeenCalledTimes(1);
+    expect(headlessLoaderMock.initializeWithHeadless).toHaveBeenCalledWith(
+      expect.any(QuanticSummary),
+      engineMock.id,
+      expect.any(Function)
+    );
+    expect(headlessLoaderMock.initializeWithHeadless).toHaveBeenCalledTimes(1);
+    expect(headlessMock.buildQuerySummary).toHaveBeenCalledWith(engineMock);
+    expect(headlessMock.buildQuerySummary).toHaveBeenCalledTimes(1);
+    expect(summaryControllerMock.subscribe).toHaveBeenCalledWith(
+      expect.any(Function)
+    );
+    expect(summaryControllerMock.subscribe).toHaveBeenCalledTimes(1);
+  });
+
+  it('should not render before results have returned', () => {
+    summaryControllerMock.state.hasResults = false;
+
+    const element = createTestComponent();
+
+    const summary = element.shadowRoot.querySelector(selectors.summary);
+    expect(summary).toBeFalsy();
+  });
+
+  describe('when loading default summary', () => {
+    it('should render the summary correctly', async () => {
+      const element = createTestComponent();
+      await updateState();
+
+      const summary = element.shadowRoot.querySelector(selectors.summary);
+      expect(summary).toBeTruthy();
+      expect(summary.value).toBe(
+        `${summaryControllerMock.state.firstResult}-${summaryControllerMock.state.lastResult} ${summaryControllerMock.state.total} `
+      );
+      expect(
+        quanticUtilsMock.I18nUtils.getTextWithDecorator
+      ).toHaveBeenCalledWith(
+        `${summaryControllerMock.state.total}`,
+        '<b class="summary__total">',
+        '</b>'
+      );
+      expect(
+        quanticUtilsMock.I18nUtils.getTextWithDecorator
+      ).toHaveBeenCalledWith(
+        `${summaryControllerMock.state.firstResult}-${summaryControllerMock.state.lastResult}`,
+        '<b class="summary__range">',
+        '</b>'
+      );
+      expect(
+        quanticUtilsMock.I18nUtils.getLabelNameWithCount
+      ).toHaveBeenCalledWith(
+        'showingResultsOf',
+        summaryControllerMock.state.lastResult
+      );
+    });
+  });
+
+  describe('when there is an initialization error', () => {
+    it('should show the error component', async () => {
+      headlessLoaderMock.initializeWithHeadless.mockImplementationOnce(
+        async (element) => {
+          if (element instanceof QuanticSummary) {
+            element.setInitializationError();
+          }
+        }
+      );
+
+      const element = createTestComponent();
+      await flushPromises();
+
+      const error = element.shadowRoot.querySelector(selectors.componentError);
+      expect(error).toBeTruthy();
+    });
+
+    describe('when a query yields no results', () => {
+      it('should not render anything', async () => {
+        summaryControllerMock.state.hasResults = false;
+        const element = createTestComponent();
+        await updateState();
+
+        const summary = element.shadowRoot.querySelector(selectors.summary);
+        expect(summary).toBeFalsy();
path: packages/quantic/force-app/main/default/lwc/quanticSummary/__tests__/quanticSummary.test.js
user: login: erocheleau
body: 
```suggestion
        expect(summary).toBeNull();
```
created_at: 2025-04-23T22:28:37Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,75 @@
+import {useCaseTestCases} from '../../../../../../playwright/utils/useCase';
+import {testSearch, testInsight, expect} from './fixture';
+
+const fixtures = {
+  search: testSearch as typeof testSearch,
+  insight: testInsight as typeof testInsight,
+};
+
+useCaseTestCases.forEach((useCase) => {
+  let test = fixtures[useCase.value];
+  test.describe(`quantic-summary ${useCase.label}`, () => {
+    test.describe('when selecting result per page', () => {
+      test('should display the correct per-page number and log analytics', async ({
path: packages/quantic/force-app/main/default/lwc/quanticSummary/e2e/quanticSummary.e2e.ts
user: login: mikegron
body: ahh ok, j'avoue que le summary lui meme ne log rien.. bon je vais deleter ca lol 🙃
created_at: 2025-04-24T15:03:03Z
author_association: CONTRIBUTOR

PR comments:
user: login: developer-experience-bot[bot]
created_at: 2025-04-16T18:36:24Z
body: # Pull Request Report

## PR Title

:white_check_mark: Title follows the [conventional commit](https://www.conventionalcommits.org/en/v1.0.0/#summary) spec.

## Live demo links

* [Storybook](https://coveo.github.io/ui-kit-prs/5183/dist-storybook/)

* [Playwright report](https://coveo.github.io/ui-kit-prs/5183/playwright-report/)

## Bundle Size


| File | Old (kb) | New (kb) | Change (%)
| ---- |:--------:|:--------:|:------:
|case-assist|248.8|248.8|0
|commerce|361.1|361.1|0
|search|419.8|419.8|0
|insight|410.9|410.9|0
|recommendation|259.6|259.6|0
|ssr|413.5|413.5|0
|ssr-commerce|377.6|377.6|0

<!-- pr-report -->
author_association: CONTRIBUTOR
user: login: mmitiche
created_at: 2025-04-17T18:06:29Z
body: > https://coveord.atlassian.net/browse/SFINT-6011
> 
> Replacing Cypress tests for Quantic Summary!
> 
> Simple component but almost everything has to be tested using playwright since it relies on lightning-rich-text 


@mikegron  I don't think that the Lightning Rich Text component is an obstacle to unit test this component.
Since the  Lightning Rich Text component is a Salesforce component we could take it for granted that this component will work as expected and concentrate on testing the Quantic Summary component in isolation.
To implement such tests I would focalize  my testes on the attributes passed to Lightning Rich Text component namely the `value` attribute.

As soon as the correct value is passed to the `value` attribute of  the Lightning Rich Text component in the different situations the Quantic summary component supports, then it's enough for me to say that the Quantic Summary is very well covered with unit tests.



author_association: CONTRIBUTOR
user: login: mmitiche
created_at: 2025-04-22T12:11:48Z
body: > I think you should add a test for the analytics sent. For example, when we change the results per page, it should send a `pagerResize` event : <img alt="image" width="1388" src="https://private-user-images.githubusercontent.com/73316533/435250260-a1605902-ddf5-4763-9040-ed5675b473a9.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDUzMjM4MjQsIm5iZiI6MTc0NTMyMzUyNCwicGF0aCI6Ii83MzMxNjUzMy80MzUyNTAyNjAtYTE2MDU5MDItZGRmNS00NzYzLTkwNDAtZWQ1Njc1YjQ3M2E5LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA0MjIlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNDIyVDEyMDUyNFomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTMzMWE1NWVhNGRjNDI5YzhjMTQxNzMzNGU5ZTQ2NWUxNTUwMjVmYWY2NjM4NzFiNjQ5YTA3M2FmMmE3M2I3NmYmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.HBsJJW7pNrGYXkzbMKgzaVOJO2i-3YH7NdJDIyQA-p4">

@SimonMilord testing the analytics for page resize makes more sense in the E2E of the QuanticResultsPerPage component and not the Quantic Summary component,
author_association: CONTRIBUTOR
user: login: mikegron
created_at: 2025-04-22T13:54:01Z
body: > I think you should add a test for the analytics sent. For example, when we change the results per page, it should send a `pagerResize` event : <img alt="image" width="1388" src="https://private-user-images.githubusercontent.com/73316533/435250260-a1605902-ddf5-4763-9040-ed5675b473a9.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDUzMzAyNTIsIm5iZiI6MTc0NTMyOTk1MiwicGF0aCI6Ii83MzMxNjUzMy80MzUyNTAyNjAtYTE2MDU5MDItZGRmNS00NzYzLTkwNDAtZWQ1Njc1YjQ3M2E5LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA0MjIlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNDIyVDEzNTIzMlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTVhYjM3MjlkODNiY2ZiZWZiZGY1ZmRjZDIyOWViOTY3MzFiMTdjOTQxOWQ2YWYwYTFmYmNlZWEyMzZlNjczZDImWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.n40PXGrc-Mg-6U5r32JBdceHCwdG5wW7JIuSfSUXw84">

nice j'avais meme pas vue que ca shootais des analytics
author_association: CONTRIBUTOR