From 2488ae8319ae4a74be4340ed467dcaf654758ab3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 30 Jul 2024 12:58:45 +0000 Subject: [PATCH 01/23] chore(deps): update react j:kit-282 (#2957) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [@testing-library/react](https://togithub.com/testing-library/react-testing-library) | [`14.3.0` -> `14.3.1`](https://renovatebot.com/diffs/npm/@testing-library%2freact/14.3.0/14.3.1) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@testing-library%2freact/14.3.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@testing-library%2freact/14.3.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@testing-library%2freact/14.3.0/14.3.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@testing-library%2freact/14.3.0/14.3.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | | [@types/react](https://togithub.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/react) ([source](https://togithub.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react)) | [`18.2.21` -> `18.3.3`](https://renovatebot.com/diffs/npm/@types%2freact/18.0.17/18.3.3) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@types%2freact/18.3.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@types%2freact/18.3.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@types%2freact/18.0.17/18.3.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@types%2freact/18.0.17/18.3.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | | [@types/react](https://togithub.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/react) ([source](https://togithub.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react)) | [`18.0.17` -> `18.3.3`](https://renovatebot.com/diffs/npm/@types%2freact/18.0.17/18.3.3) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@types%2freact/18.3.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@types%2freact/18.3.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@types%2freact/18.0.17/18.3.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@types%2freact/18.0.17/18.3.3?slim=true)](https://docs.renovatebot.com/merge-confidence/) | | [@types/react-dom](https://togithub.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/react-dom) ([source](https://togithub.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom)) | [`18.2.7` -> `18.3.0`](https://renovatebot.com/diffs/npm/@types%2freact-dom/18.0.6/18.3.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@types%2freact-dom/18.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@types%2freact-dom/18.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@types%2freact-dom/18.0.6/18.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@types%2freact-dom/18.0.6/18.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | | [@types/react-dom](https://togithub.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/react-dom) ([source](https://togithub.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom)) | [`18.0.6` -> `18.3.0`](https://renovatebot.com/diffs/npm/@types%2freact-dom/18.0.6/18.3.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@types%2freact-dom/18.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@types%2freact-dom/18.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@types%2freact-dom/18.0.6/18.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@types%2freact-dom/18.0.6/18.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | | [eslint-plugin-react](https://togithub.com/jsx-eslint/eslint-plugin-react) | [`7.30.1` -> `7.35.0`](https://renovatebot.com/diffs/npm/eslint-plugin-react/7.33.2/7.35.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/eslint-plugin-react/7.35.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/eslint-plugin-react/7.35.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/eslint-plugin-react/7.33.2/7.35.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/eslint-plugin-react/7.33.2/7.35.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | | [eslint-plugin-react](https://togithub.com/jsx-eslint/eslint-plugin-react) | [`7.33.2` -> `7.35.0`](https://renovatebot.com/diffs/npm/eslint-plugin-react/7.33.2/7.35.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/eslint-plugin-react/7.35.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/eslint-plugin-react/7.35.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/eslint-plugin-react/7.33.2/7.35.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/eslint-plugin-react/7.33.2/7.35.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | | [react](https://reactjs.org/) ([source](https://togithub.com/facebook/react/tree/HEAD/packages/react)) | [`18.2.0` -> `18.3.1`](https://renovatebot.com/diffs/npm/react/18.2.0/18.3.1) | [![age](https://developer.mend.io/api/mc/badges/age/npm/react/18.3.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/react/18.3.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/react/18.2.0/18.3.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/react/18.2.0/18.3.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | | [react-dom](https://reactjs.org/) ([source](https://togithub.com/facebook/react/tree/HEAD/packages/react-dom)) | [`18.2.0` -> `18.3.1`](https://renovatebot.com/diffs/npm/react-dom/18.2.0/18.3.1) | [![age](https://developer.mend.io/api/mc/badges/age/npm/react-dom/18.3.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/react-dom/18.3.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/react-dom/18.2.0/18.3.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/react-dom/18.2.0/18.3.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | | [react-router-dom](https://togithub.com/remix-run/react-router) ([source](https://togithub.com/remix-run/react-router/tree/HEAD/packages/react-router-dom)) | [`6.16.0` -> `6.25.1`](https://renovatebot.com/diffs/npm/react-router-dom/6.16.0/6.25.1) | [![age](https://developer.mend.io/api/mc/badges/age/npm/react-router-dom/6.25.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/react-router-dom/6.25.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/react-router-dom/6.16.0/6.25.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/react-router-dom/6.16.0/6.25.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes
testing-library/react-testing-library (@​testing-library/react) ### [`v14.3.1`](https://togithub.com/testing-library/react-testing-library/releases/tag/v14.3.1) [Compare Source](https://togithub.com/testing-library/react-testing-library/compare/v14.3.0...v14.3.1) ##### Bug Fixes - Stop using nullish coalescing ([#​1300](https://togithub.com/testing-library/react-testing-library/issues/1300)) ([8434a24](https://togithub.com/testing-library/react-testing-library/commit/8434a24ce79583d1359f58563c5f3981e729eb1c))
jsx-eslint/eslint-plugin-react (eslint-plugin-react) ### [`v7.35.0`](https://togithub.com/jsx-eslint/eslint-plugin-react/blob/HEAD/CHANGELOG.md#7350---20240719) [Compare Source](https://togithub.com/jsx-eslint/eslint-plugin-react/compare/v7.34.4...v7.35.0) ##### Added - support eslint v9 ([#​3759][] [@​mdjermanovic](https://togithub.com/mdjermanovic)) - export flat configs from plugin root and fix flat config crash ([#​3694][] [@​bradzacher](https://togithub.com/bradzacher) [@​mdjermanovic](https://togithub.com/mdjermanovic)) - add \[`jsx-props-no-spread-multi`] ([#​3724][] [@​SimonSchick](https://togithub.com/SimonSchick)) - \[`forbid-component-props`]: add `propNamePattern` to allow / disallow prop name patterns ([#​3774][] [@​akulsr0](https://togithub.com/akulsr0)) - \[`jsx-handler-names`]: support ignoring component names ([#​3772][] [@​akulsr0](https://togithub.com/akulsr0)) - version settings: Allow react defaultVersion to be configurable ([#​3771][] [@​onlywei](https://togithub.com/onlywei)) - \[`jsx-closing-tag-location`]: add `line-aligned` option ([#​3777] [@​kimtaejin3](https://togithub.com/kimtaejin3)) - \[`no-danger`]: add `customComponentNames` option ([#​3748][] [@​akulsr0](https://togithub.com/akulsr0)) ##### Fixed - \[`no-invalid-html-attribute`]: substitute placeholders in suggestion messages ([#​3759][] [@​mdjermanovic](https://togithub.com/mdjermanovic)) - \[`sort-prop-types`]: single line type ending without semicolon ([#​3784][] [@​akulsr0](https://togithub.com/akulsr0)) - \[`require-default-props`]: report when required props have default value ([#​3785][] [@​akulsr0](https://togithub.com/akulsr0)) ##### Changed - \[Refactor] `variableUtil`: Avoid creating a single flat variable scope for each lookup ([#​3782][] [@​DanielRosenwasser](https://togithub.com/DanielRosenwasser)) [7.35.0]: https://togithub.com/jsx-eslint/eslint-plugin-react/compare/v7.34.4...v7.35.0 [#​3785]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/3785 [#​3784]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/3784 [#​3782]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/3782 [#​3777]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/3777 [#​3774]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/3774 [#​3772]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/3772 [#​3771]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/3771 [#​3759]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/3759 [#​3748]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/3748 [#​3724]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/3724 [#​3694]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/3694 ### [`v7.34.4`](https://togithub.com/jsx-eslint/eslint-plugin-react/blob/HEAD/CHANGELOG.md#7344---20240713) [Compare Source](https://togithub.com/jsx-eslint/eslint-plugin-react/compare/v7.34.3...v7.34.4) ##### Fixed - \[`prop-types`]: fix `className` missing in prop validation false negative ([#​3749] [@​akulsr0](https://togithub.com/akulsr0)) - \[`sort-prop-types`]: Check for undefined before accessing `node.typeAnnotation.typeAnnotation` ([#​3779] [@​tylerlaprade](https://togithub.com/tylerlaprade)) [7.34.4]: https://togithub.com/jsx-eslint/eslint-plugin-react/compare/v7.34.3...v7.34.4 [#​3779]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/3779 [#​3749]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/3749 ### [`v7.34.3`](https://togithub.com/jsx-eslint/eslint-plugin-react/blob/HEAD/CHANGELOG.md#7343---20240618) [Compare Source](https://togithub.com/jsx-eslint/eslint-plugin-react/compare/v7.34.2...v7.34.3) ##### Fixed - \[`prop-types`]: null-check rootNode before calling getScope ([#​3762][] [@​crnhrv](https://togithub.com/crnhrv)) - \[`boolean-prop-naming`]: avoid a crash with a spread prop ([#​3733][] [@​ljharb](https://togithub.com/ljharb)) - \[`jsx-boolean-value`]: `assumeUndefinedIsFalse` with `never` must not allow explicit `true` value ([#​3757][] [@​6uliver](https://togithub.com/6uliver)) - \[`no-object-type-as-default-prop`]: enable rule for components with many parameters ([#​3768][] [@​JulienR1](https://togithub.com/JulienR1)) - \[`jsx-key`]: incorrect behavior for checkKeyMustBeforeSpread with map callbacks ([#​3769][] [@​akulsr0](https://togithub.com/akulsr0)) [7.34.3]: https://togithub.com/jsx-eslint/eslint-plugin-react/compare/v7.34.2...v7.34.3 [#​3769]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/3769 [#​3768]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/3768 [#​3762]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/3762 [#​3757]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/3757 [#​3733]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/3733 ### [`v7.34.2`](https://togithub.com/jsx-eslint/eslint-plugin-react/releases/tag/v7.34.2) [Compare Source](https://togithub.com/jsx-eslint/eslint-plugin-react/compare/v7.34.1...v7.34.2) ##### Fixed - [`boolean-prop-naming`][boolean-prop-naming]: avoid a crash with a non-TSTypeReference type ([#​3718][] [@​developer-bandi](https://togithub.com/developer-bandi)) - [`jsx-no-leaked-render`][jsx-no-leaked-render]: invalid report if left side is boolean ([#​3746][] [@​akulsr0](https://togithub.com/akulsr0)) - [`jsx-closing-bracket-location`][jsx-closing-bracket-location]: message shows `{{details}}` when there are no details ([#​3759][] [@​mdjermanovic](https://togithub.com/mdjermanovic)) - [`no-invalid-html-attribute`][no-invalid-html-attribute]: ensure error messages are correct ([#​3759][] [@​mdjermanovic](https://togithub.com/mdjermanovic), [@​ljharb](https://togithub.com/ljharb)) ##### Changed - \[Refactor] create various eslint utils to fix eslint deprecations ([#​3759][] [@​mdjermanovic](https://togithub.com/mdjermanovic), [@​ljharb](https://togithub.com/ljharb)) [7.34.2]: https://togithub.com/jsx-eslint/eslint-plugin-react/compare/v7.34.1...v7.34.2 [#​3759]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/3759 [#​3746]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/3746 [#​3718]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/3718 [`boolean-prop-naming`]: docs/rules/boolean-prop-naming.md [`jsx-no-leaked-render`]: docs/rules/jsx-no-leaked-render.md [`jsx-closing-bracket-location`]: docs/rules/jsx-closing-bracket-location.md [`no-invalid-html-attribute`]: docs/rules/no-invalid-html-attribute.md ### [`v7.34.1`](https://togithub.com/jsx-eslint/eslint-plugin-react/releases/tag/v7.34.1) [Compare Source](https://togithub.com/jsx-eslint/eslint-plugin-react/compare/v7.34.0...v7.34.1) ##### Fixed - [`jsx-no-leaked-render`][jsx-no-leaked-render]: prevent wrongly adding parens ([#​3700][] [@​developer-bandi](https://togithub.com/developer-bandi)) - [`boolean-prop-naming`][boolean-prop-naming]: detect TS interfaces ([#​3701][] [@​developer-bandi](https://togithub.com/developer-bandi)) - [`boolean-prop-naming`][boolean-prop-naming]: literalType error fix ([#​3704][] [@​developer-bandi](https://togithub.com/developer-bandi)) - [`boolean-prop-naming`][boolean-prop-naming]: allow TSIntersectionType ([#​3705][] [@​developer-bandi](https://togithub.com/developer-bandi)) - [`no-unknown-property`][no-unknown-property]: support `popover`, `popovertarget`, `popovertargetaction` attributes ([#​3707][] [@​ljharb](https://togithub.com/ljharb)) - [`no-unknown-property`][no-unknown-property]: only match `data-*` attributes containing `-` ([#​3713][] [@​silverwind](https://togithub.com/silverwind)) - [`checked-requires-onchange-or-readonly`][checked-requires-onchange-or-readonly]: correct options that were behaving opposite ([#​3715][] [@​jaesoekjjang](https://togithub.com/jaesoekjjang)) ##### Changed - [`boolean-prop-naming`][boolean-prop-naming]: improve error message ([@​ljharb](https://togithub.com/ljharb)) [#​1000]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1000 [#​1002]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1002 [#​1005]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1005 [#​100]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/100 [#​1010]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1010 [#​1013]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1013 [#​1022]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1022 [#​1029]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1029 [#​102]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/102 [#​1034]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1034 [#​1038]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1038 [#​1041]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1041 [#​1043]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1043 [#​1046]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1046 [#​1047]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1047 [#​1050]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1050 [#​1053]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1053 [#​1057]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1057 [#​105]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/105 [#​1061]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1061 [#​1062]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1062 [#​1070]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1070 [#​1071]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1071 [#​1073]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1073 [#​1076]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1076 [#​1079]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1079 [#​1088]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1088 [#​1098]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1098 [#​1101]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1101 [#​1103]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1103 [#​110]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/110 [#​1116]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1116 [#​1117]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1117 [#​1119]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1119 [#​1121]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1121 [#​1122]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1122 [#​1123]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1123 [#​1130]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1130 [#​1131]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1131 [#​1132]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1132 [#​1134]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1134 [#​1135]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1135 [#​1139]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1139 [#​1148]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1148 [#​1149]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1149 [#​114]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/114 [#​1151]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1151 [#​1155]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1155 [#​1161]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1161 [#​1167]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1167 [#​1173]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1173 [#​1174]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1174 [#​1175]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1175 [#​1178]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1178 [#​1179]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1179 [#​117]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/117 [#​1180]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1180 [#​1183]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1183 [#​1189]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1189 [#​118]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/118 [#​1192]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1192 [#​1195]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1195 [#​1199]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1199 [#​119]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/119 [#​11]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/11 [#​1201]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1201 [#​1202]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1202 [#​1206]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1206 [#​1213]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1213 [#​1216]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1216 [#​1222]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1222 [#​1226]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1226 [#​1227]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1227 [#​122]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/122 [#​1231]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1231 [#​1236]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1236 [#​1239]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1239 [#​123]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/123 [#​1241]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1241 [#​1242]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1242 [#​1246]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1246 [#​1249]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1249 [#​1253]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1253 [#​1257]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1257 [#​125]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/125 [#​1260]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1260 [#​1261]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1261 [#​1262]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1262 [#​1264]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1264 [#​1266]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1266 [#​1269]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1269 [#​1273]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1273 [#​1274]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1274 [#​1277]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1277 [#​127]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/127 [#​1281]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1281 [#​1287]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1287 [#​1288]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1288 [#​1289]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1289 [#​128]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/128 [#​1290]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1290 [#​1294]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1294 [#​1296]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1296 [#​129]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/129 [#​12]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/12 [#​1301]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1301 [#​1303]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1303 [#​1306]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1306 [#​1308]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1308 [#​1309]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1309 [#​130]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/130 [#​1310]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1310 [#​1323]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1323 [#​1329]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1329 [#​132]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/132 [#​1335]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1335 [#​1337]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1337 [#​133]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/133 [#​1344]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1344 [#​1352]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1352 [#​1353]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1353 [#​1354]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1354 [#​135]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/135 [#​1361]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1361 [#​1363]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1363 [#​1364]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1364 [#​1366]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1366 [#​1369]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1369 [#​136]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/136 [#​1374]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1374 [#​1376]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1376 [#​137]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/137 [#​1380]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1380 [#​1381]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1381 [#​1382]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1382 [#​1383]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1383 [#​1384]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1384 [#​1386]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1386 [#​1388]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1388 [#​1389]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1389 [#​138]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/138 [#​1392]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1392 [#​1395]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1395 [#​1396]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1396 [#​1398]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1398 [#​139]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/139 [#​13]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/13 [#​1400]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1400 [#​1403]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1403 [#​1406]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1406 [#​1409]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1409 [#​1412]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1412 [#​1413]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1413 [#​1414]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1414 [#​1417]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1417 [#​1422]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1422 [#​1423]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1423 [#​142]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/142 [#​1432]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1432 [#​1435]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1435 [#​1438]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1438 [#​1444]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1444 [#​1449]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1449 [#​144]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/144 [#​1450]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1450 [#​145]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/145 [#​1462]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1462 [#​1464]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1464 [#​1467]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1467 [#​1468]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1468 [#​146]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/146 [#​1471]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1471 [#​1475]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1475 [#​1476]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1476 [#​1478]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1478 [#​1479]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1479 [#​147]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/147 [#​1485]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1485 [#​148]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/148 [#​1493]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1493 [#​1494]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1494 [#​1496]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1496 [#​1497]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1497 [#​1499]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1499 [#​14]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/14 [#​1500]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1500 [#​1502]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1502 [#​1507]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1507 [#​1508]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1508 [#​1511]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1511 [#​1512]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1512 [#​1514]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1514 [#​1515]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1515 [#​1517]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1517 [#​1518]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1518 [#​1521]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1521 [#​1524]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1524 [#​1525]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1525 [#​1526]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1526 [#​1530]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1530 [#​1533]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1533 [#​1538]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1538 [#​1542]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1542 [#​1543]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1543 [#​1546]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1546 [#​1547]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1547 [#​154]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/154 [#​1552]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1552 [#​1559]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1559 [#​1562]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1562 [#​1566]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1566 [#​156]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/156 [#​1571]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1571 [#​1572]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1572 [#​1576]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1576 [#​1578]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1578 [#​1581]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1581 [#​1588]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1588 [#​1591]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1591 [#​1595]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1595 [#​1597]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1597 [#​159]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/159 [#​15]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/15 [#​1607]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1607 [#​1610]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1610 [#​1611]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1611 [#​1617]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1617 [#​161]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/161 [#​1621]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1621 [#​1624]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1624 [#​1635]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1635 [#​1636]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1636 [#​163]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/163 [#​1642]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1642 [#​1644]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1644 [#​164]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/164 [#​1650]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1650 [#​1653]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1653 [#​1655]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1655 [#​1657]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1657 [#​1659]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1659 [#​165]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/165 [#​1665]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1665 [#​1666]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1666 [#​1669]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1669 [#​1670]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1670 [#​1675]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1675 [#​1677]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1677 [#​167]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/167 [#​1681]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1681 [#​1689]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1689 [#​1690]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1690 [#​1699]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1699 [#​16]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/16 [#​1703]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1703 [#​1717]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1717 [#​1722]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1722 [#​1724]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1724 [#​1728]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1728 [#​172]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/172 [#​1732]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1732 [#​1737]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1737 [#​1742]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1742 [#​1743]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1743 [#​1749]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1749 [#​1750]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1750 [#​1753]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1753 [#​1754]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1754 [#​1755]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1755 [#​1758]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1758 [#​1759]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1759 [#​1764]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1764 [#​1767]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1767 [#​176]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/176 [#​1779]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1779 [#​1783]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1783 [#​1785]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1785 [#​178]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/178 [#​1791]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1791 [#​1793]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1793 [#​1794]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1794 [#​1796]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1796 [#​1804]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1804 [#​1805]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1805 [#​1806]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1806 [#​1815]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1815 [#​1817]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1817 [#​1819]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1819 [#​181]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/181 [#​1824]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1824 [#​1825]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1825 [#​1827]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1827 [#​1828]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1828 [#​1829]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1829 [#​182]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/182 [#​1830]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1830 [#​1831]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1831 [#​183]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/183 [#​1843]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1843 [#​1844]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1844 [#​1845]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1845 [#​1849]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1849 [#​184]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/184 [#​1851]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1851 [#​1854]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1854 [#​1857]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1857 [#​1858]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1858 [#​1860]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1860 [#​1861]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1861 [#​1863]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1863 [#​1867]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1867 [#​1868]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1868 [#​1873]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1873 [#​1874]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1874 [#​187]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/187 [#​1880]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1880 [#​1883]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1883 [#​1890]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1890 [#​1891]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1891 [#​1892]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1892 [#​1898]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1898 [#​189]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/189 [#​1903]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1903 [#​1905]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1905 [#​1907]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1907 [#​1909]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1909 [#​1911]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1911 [#​1914]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1914 [#​1918]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1918 [#​1924]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1924 [#​1926]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1926 [#​1929]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1929 [#​192]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/192 [#​1932]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1932 [#​1939]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1939 [#​193]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/193 [#​1941]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1941 [#​1942]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1942 [#​1945]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1945 [#​1946]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1946 [#​1949]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1949 [#​1953]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1953 [#​1956]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1956 [#​1957]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/1957 [#​1977]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1977 [#​1978]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1978 [#​197]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/197 [#​1980]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1980 [#​1983]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1983 [#​1984]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1984 [#​1988]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1988 [#​1989]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1989 [#​198]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/198 [#​1994]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1994 [#​1995]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/1995 [#​199]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/199 [#​2001]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2001 [#​2002]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2002 [#​2004]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2004 [#​2006]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2006 [#​2008]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2008 [#​2012]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2012 [#​2015]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2015 [#​2016]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2016 [#​201]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/201 [#​2026]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2026 [#​2029]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2029 [#​2032]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2032 [#​2040]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2040 [#​2043]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2043 [#​2044]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2044 [#​2051]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/2051 [#​2056]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/2056 [#​2061]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/2061 [#​2064]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2064 [#​2065]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2065 [#​2067]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2067 [#​2069]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2069 [#​206]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/206 [#​2075]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2075 [#​2082]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/2082 [#​2084]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2084 [#​2085]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2085 [#​2086]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2086 [#​2089]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2089 [#​208]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/208 [#​2090]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2090 [#​2095]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/2095 [#​2097]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2097 [#​2098]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2098 [#​2099]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2099 [#​2100]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/2100 [#​2102]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/2102 [#​2103]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2103 [#​2104]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/2104 [#​2109]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/2109 [#​210]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/210 [#​2110]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2110 [#​2111]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/2111 [#​2113]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/2113 [#​2114]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/2114 [#​2115]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/2115 [#​2116]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2116 [#​2117]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/2117 [#​2118]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/2118 [#​211]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/211 [#​2120]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/2120 [#​2123]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/2123 [#​2125]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2125 [#​2127]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/2127 [#​2128]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/2128 [#​2131]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/2131 [#​2134]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/2134 [#​2136]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/2136 [#​2137]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/2137 [#​213]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/213 [#​2143]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2143 [#​2145]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/2145 [#​2146]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2146 [#​2147]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/2147 [#​214]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/214 [#​215]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/215 [#​2166]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2166 [#​2167]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2167 [#​217]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/217 [#​2180]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2180 [#​2182]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2182 [#​2183]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/2183 [#​2184]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2184 [#​2191]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2191 [#​2193]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2193 [#​2198]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2198 [#​219]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/219 [#​2200]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2200 [#​2202]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2202 [#​2203]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2203 [#​2206]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2206 [#​2207]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2207 [#​220]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/220 [#​2210]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2210 [#​221]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/221 [#​2225]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2225 [#​2227]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2227 [#​2229]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2229 [#​222]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/222 [#​2230]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2230 [#​2232]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2232 [#​2233]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2233 [#​2234]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2234 [#​2238]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2238 [#​2246]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2246 [#​2250]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2250 [#​2256]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2256 [#​2259]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2259 [#​2261]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2261 [#​2262]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2262 [#​2263]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2263 [#​2265]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2265 [#​2267]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2267 [#​226]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/226 [#​2273]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2273 [#​2274]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2274 [#​2276]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/2276 [#​2283]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/2283 [#​2286]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2286 [#​2288]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2288 [#​228]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/228 [#​2292]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2292 [#​2294]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2294 [#​2295]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2295 [#​2298]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/2298 [#​229]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/229 [#​22]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/22 [#​2302]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2302 [#​2303]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2303 [#​2304]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2304 [#​230]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/230 [#​2312]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/2312 [#​2316]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2316 [#​2319]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/2319 [#​2326]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/2326 [#​232]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/232 [#​2330]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/2330 [#​2336]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2336 [#​233]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/233 [#​2349]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/2349 [#​2354]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2354 [#​2359]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2359 [#​235]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/235 [#​2361]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2361 [#​2364]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2364 [#​2367]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2367 [#​236]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/236 [#​2375]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2375 [#​2378]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2378 [#​237]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/237 [#​2380]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2380 [#​2383]: https://togithub.com/jsx-eslint/eslint-plugin-react/issue/2383 [#​2385]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2385 [#​2391]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2391 [#​2392]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2392 [#​2395]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2395 [#​2399]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2399 [#​23]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/23 [#​2402]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2402 [#​2408]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2408 [#​2409]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2409 [#​240]: https://togithub.com/jsx-eslint/eslint-plugin-react/issues/240 [#​2410]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2410 [#​2414]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2414 [#​2419]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2419 [#​2422]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2422 [#​2425]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2425 [#​2426]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2426 [#​2428]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2428 [#​2429]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2429 [#​242]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/242 [#​2431]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2431 [#​2436]: https://togithub.com/jsx-eslint/eslint-plugin-react/pull/2436 [#​2437]: https://togithub.com/jsx-eslint/eslin
--- ### Configuration 📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://togithub.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View the [repository job log](https://developer.mend.io/github/coveo/ui-kit). --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Alex Prudhomme <78121423+alexprudhomme@users.noreply.github.com> --- package-lock.json | 603 +++++++++++------- package.json | 4 +- packages/atomic-react/package.json | 8 +- packages/headless-react/package.json | 8 +- packages/samples/atomic-next/package.json | 8 +- packages/samples/atomic-react/package.json | 10 +- .../headless-commerce-react/package.json | 12 +- packages/samples/headless-react/package.json | 12 +- .../headless-ssr/app-router/package.json | 8 +- packages/samples/headless-ssr/package.json | 8 +- .../headless-ssr/pages-router/package.json | 8 +- packages/samples/iife/package.json | 4 +- 12 files changed, 425 insertions(+), 268 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5b6ef285df6..4a8f6cb5b22 100644 --- a/package-lock.json +++ b/package-lock.json @@ -83,8 +83,8 @@ "nx": "19.0.4", "patch-package": "8.0.0", "prettier": "3.3.3", - "react": "18.2.0", - "react-dom": "18.2.0", + "react": "18.3.1", + "react-dom": "18.3.1", "react-syntax-highlighter": "15.5.0", "rimraf": "5.0.9", "semver": "7.6.3", @@ -141,7 +141,8 @@ "node_modules/@adobe/css-tools": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.3.tgz", - "integrity": "sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ==" + "integrity": "sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ==", + "dev": true }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", @@ -11234,9 +11235,10 @@ } }, "node_modules/@remix-run/router": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.9.0.tgz", - "integrity": "sha512-bV63itrKBC0zdT27qYm6SDZHlkXwFL1xMBuhkn+X7l0+IIhNaH5wuuvZKp6eKhCD4KFhujhfhCT1YxXW6esUIA==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.18.0.tgz", + "integrity": "sha512-L3jkqmqoSVBVKHfpGZmLrex0lxR5SucGA0sUfFzGctehw+S/ggL9L/0NnC5mw6P8HUWpFZ3nQw3cRApjjWx9Sw==", + "license": "MIT", "engines": { "node": ">=14.0.0" } @@ -20887,9 +20889,10 @@ } }, "node_modules/@testing-library/react": { - "version": "14.3.0", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.3.0.tgz", - "integrity": "sha512-AYJGvNFMbCa5vt1UtDCa/dcaABrXq8gph6VN+cffIx0UeA0qiGqS+sT60+sb+Gjc8tGXdECWYQgaF0khf8b+Lg==", + "version": "14.3.1", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.3.1.tgz", + "integrity": "sha512-H99XjUhWQw0lTgyMN05W3xQG1Nh4lq574D8keFf1dDoNTJgp66VbJozRaczoF+wsiaPJNt/TcnfpLGufGxSrZQ==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.12.5", "@testing-library/dom": "^9.0.0", @@ -21437,6 +21440,7 @@ "version": "29.5.12", "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz", "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==", + "dev": true, "dependencies": { "expect": "^29.0.0", "pretty-format": "^29.0.0" @@ -21654,19 +21658,20 @@ "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" }, "node_modules/@types/react": { - "version": "18.0.17", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.17.tgz", - "integrity": "sha512-38ETy4tL+rn4uQQi7mB81G7V1g0u2ryquNmsVIOKUAEIDK+3CUjZ6rSRpdvS99dNBnkLFL83qfmtLacGOTIhwQ==", + "version": "18.3.3", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", + "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", + "license": "MIT", "dependencies": { "@types/prop-types": "*", - "@types/scheduler": "*", "csstype": "^3.0.2" } }, "node_modules/@types/react-dom": { - "version": "18.0.6", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.6.tgz", - "integrity": "sha512-/5OFZgfIPSwy+YuIBP/FgJnQnsxhZhjjrnxudMddeblOouIodEQ75X14Rr4wGSG/bknL+Omy9iWlLo1u/9GzAA==", + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", + "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", + "license": "MIT", "dependencies": { "@types/react": "*" } @@ -21707,7 +21712,8 @@ "node_modules/@types/scheduler": { "version": "0.16.8", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", - "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==" + "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", + "dev": true }, "node_modules/@types/semver": { "version": "7.5.8", @@ -21781,14 +21787,6 @@ "integrity": "sha512-9ytlUoDq2/vhwmpBVWI4BFfWJVsKJl+xogC1TYZSkhnmMUHvVVD71eHaPV3j4YnLViIJcai8BVdv2BGJpwQfFQ==", "dev": true }, - "node_modules/@types/testing-library__jest-dom": { - "version": "5.14.9", - "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.9.tgz", - "integrity": "sha512-FSYhIjFlfOpGSRyVoMBMuS3ws5ehFQODymf3vlI7U1K8c7PHwWwFY7VREfmsuzHSOnoKs/9/Y983ayOs7eRzqw==", - "dependencies": { - "@types/jest": "*" - } - }, "node_modules/@types/tough-cookie": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", @@ -23226,14 +23224,16 @@ "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==" }, "node_modules/array-includes": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", - "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", "is-string": "^1.0.7" }, "engines": { @@ -23275,6 +23275,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array.prototype.findlastindex": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.4.tgz", @@ -23346,15 +23366,19 @@ } }, "node_modules/array.prototype.tosorted": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.3.tgz", - "integrity": "sha512-/DdH4TiTmOKzyQbp/eadcCVexiCb36xJg7HshYOYJnNZFDj33GEv0P7GxsynpShhq4OLYJzbGcBDkLsDt7MnNg==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.5", + "call-bind": "^1.0.7", "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.1.0", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" } }, "node_modules/arraybuffer.prototype.slice": { @@ -23490,14 +23514,6 @@ "retry": "0.13.1" } }, - "node_modules/asynciterator.prototype": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz", - "integrity": "sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==", - "dependencies": { - "has-symbols": "^1.0.3" - } - }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -27612,6 +27628,57 @@ "node": ">=12" } }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/date-fns": { "version": "2.30.0", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", @@ -29181,16 +29248,21 @@ } }, "node_modules/es-abstract": { - "version": "1.22.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.5.tgz", - "integrity": "sha512-oW69R+4q2wG+Hc3KZePPZxOiisRIqfKBVo/HLx94QcJeWGU/8sZhCvc829rd1kS366vlJbzBfXf9yWwf0+Ko7w==", + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "license": "MIT", "dependencies": { "array-buffer-byte-length": "^1.0.1", "arraybuffer.prototype.slice": "^1.0.3", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", "es-define-property": "^1.0.0", "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.0.3", "es-to-primitive": "^1.2.1", "function.prototype.name": "^1.1.6", @@ -29201,10 +29273,11 @@ "has-property-descriptors": "^1.0.2", "has-proto": "^1.0.3", "has-symbols": "^1.0.3", - "hasown": "^2.0.1", + "hasown": "^2.0.2", "internal-slot": "^1.0.7", "is-array-buffer": "^3.0.4", "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", "is-negative-zero": "^2.0.3", "is-regex": "^1.1.4", "is-shared-array-buffer": "^1.0.3", @@ -29215,17 +29288,17 @@ "object-keys": "^1.1.1", "object.assign": "^4.1.5", "regexp.prototype.flags": "^1.5.2", - "safe-array-concat": "^1.1.0", + "safe-array-concat": "^1.1.2", "safe-regex-test": "^1.0.3", - "string.prototype.trim": "^1.2.8", - "string.prototype.trimend": "^1.0.7", - "string.prototype.trimstart": "^1.0.7", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.2", "typed-array-byte-length": "^1.0.1", "typed-array-byte-offset": "^1.0.2", - "typed-array-length": "^1.0.5", + "typed-array-length": "^1.0.6", "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.14" + "which-typed-array": "^1.1.15" }, "engines": { "node": ">= 0.4" @@ -29283,25 +29356,25 @@ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" }, "node_modules/es-iterator-helpers": { - "version": "1.0.17", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.17.tgz", - "integrity": "sha512-lh7BsUqelv4KUbR5a/ZTaGGIMLCjPGPqJ6q+Oq24YP0RdyptX1uzm4vvaqzk7Zx3bpl/76YLTTDj9L7uYQ92oQ==", + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.19.tgz", + "integrity": "sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw==", + "license": "MIT", "dependencies": { - "asynciterator.prototype": "^1.0.0", "call-bind": "^1.0.7", "define-properties": "^1.2.1", - "es-abstract": "^1.22.4", + "es-abstract": "^1.23.3", "es-errors": "^1.3.0", - "es-set-tostringtag": "^2.0.2", + "es-set-tostringtag": "^2.0.3", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "globalthis": "^1.0.3", "has-property-descriptors": "^1.0.2", - "has-proto": "^1.0.1", + "has-proto": "^1.0.3", "has-symbols": "^1.0.3", "internal-slot": "^1.0.7", "iterator.prototype": "^1.1.2", - "safe-array-concat": "^1.1.0" + "safe-array-concat": "^1.1.2" }, "engines": { "node": ">= 0.4" @@ -29312,6 +29385,18 @@ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.3.tgz", "integrity": "sha512-i1gCgmR9dCl6Vil6UKPI/trA69s08g/syhiDK9TG0Nf1RJjjFI+AzoWW7sPufzkgYAn861skuCwJa0pIIHYxvg==" }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-set-tostringtag": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", @@ -30818,32 +30903,35 @@ } }, "node_modules/eslint-plugin-react": { - "version": "7.33.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.33.2.tgz", - "integrity": "sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==", + "version": "7.35.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.35.0.tgz", + "integrity": "sha512-v501SSMOWv8gerHkk+IIQBkcGRGrO2nfybfj5pLxuJNFTPxxA3PSryhXTK+9pNbtkggheDdsC0E9Q8CuPk6JKA==", + "license": "MIT", "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flatmap": "^1.3.1", - "array.prototype.tosorted": "^1.1.1", + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.2", + "array.prototype.tosorted": "^1.1.4", "doctrine": "^2.1.0", - "es-iterator-helpers": "^1.0.12", + "es-iterator-helpers": "^1.0.19", "estraverse": "^5.3.0", + "hasown": "^2.0.2", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", - "object.entries": "^1.1.6", - "object.fromentries": "^2.0.6", - "object.hasown": "^1.1.2", - "object.values": "^1.1.6", + "object.entries": "^1.1.8", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.0", "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.4", + "resolve": "^2.0.0-next.5", "semver": "^6.3.1", - "string.prototype.matchall": "^4.0.8" + "string.prototype.matchall": "^4.0.11", + "string.prototype.repeat": "^1.0.0" }, "engines": { "node": ">=4" }, "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, "node_modules/eslint-plugin-react-hooks": { @@ -30861,6 +30949,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -30870,6 +30959,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "license": "Apache-2.0", "dependencies": { "esutils": "^2.0.2" }, @@ -30881,6 +30971,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -30892,6 +30983,7 @@ "version": "2.0.0-next.5", "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "license": "MIT", "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", @@ -30908,6 +31000,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" } @@ -35477,6 +35570,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "license": "MIT", + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-date-object": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", @@ -43740,26 +43848,29 @@ } }, "node_modules/object.entries": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.7.tgz", - "integrity": "sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==", + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", + "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" } }, "node_modules/object.fromentries": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", - "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -43802,6 +43913,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.3.tgz", "integrity": "sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA==", + "dev": true, "dependencies": { "define-properties": "^1.2.0", "es-abstract": "^1.22.1" @@ -43811,13 +43923,14 @@ } }, "node_modules/object.values": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", - "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -47379,9 +47492,9 @@ } }, "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "dependencies": { "loose-envify": "^1.1.0" }, @@ -47590,15 +47703,15 @@ } }, "node_modules/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "dependencies": { "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" + "scheduler": "^0.23.2" }, "peerDependencies": { - "react": "^18.2.0" + "react": "^18.3.1" } }, "node_modules/react-error-overlay": { @@ -47680,11 +47793,12 @@ } }, "node_modules/react-router": { - "version": "6.16.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.16.0.tgz", - "integrity": "sha512-VT4Mmc4jj5YyjpOi5jOf0I+TYzGpvzERy4ckNSvSh2RArv8LLoCxlsZ2D+tc7zgjxcY34oTz2hZaeX5RVprKqA==", + "version": "6.25.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.25.1.tgz", + "integrity": "sha512-u8ELFr5Z6g02nUtpPAggP73Jigj1mRePSwhS/2nkTrlPU5yEkH1vYzWNyvSnSzeeE2DNqWdH+P8OhIh9wuXhTw==", + "license": "MIT", "dependencies": { - "@remix-run/router": "1.9.0" + "@remix-run/router": "1.18.0" }, "engines": { "node": ">=14.0.0" @@ -47694,12 +47808,13 @@ } }, "node_modules/react-router-dom": { - "version": "6.16.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.16.0.tgz", - "integrity": "sha512-aTfBLv3mk/gaKLxgRDUPbPw+s4Y/O+ma3rEN1u8EgEpLpPe6gNjIsWt9rxushMHHMb7mSwxRGdGlGdvmFsyPIg==", + "version": "6.25.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.25.1.tgz", + "integrity": "sha512-0tUDpbFvk35iv+N89dWNrJp+afLgd+y4VtorJZuOCXK0kkCWjEvb3vTJM++SYvMEpbVwXKf3FjeVveVEb6JpDQ==", + "license": "MIT", "dependencies": { - "@remix-run/router": "1.9.0", - "react-router": "6.16.0" + "@remix-run/router": "1.18.0", + "react-router": "6.25.1" }, "engines": { "node": ">=14.0.0" @@ -49086,9 +49201,10 @@ } }, "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" } @@ -50679,32 +50795,51 @@ } }, "node_modules/string.prototype.matchall": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz", - "integrity": "sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==", + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", + "integrity": "sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", "has-symbols": "^1.0.3", - "internal-slot": "^1.0.5", - "regexp.prototype.flags": "^1.5.0", - "set-function-name": "^2.0.0", - "side-channel": "^1.0.4" + "internal-slot": "^1.0.7", + "regexp.prototype.flags": "^1.5.2", + "set-function-name": "^2.0.2", + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, "node_modules/string.prototype.trim": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", - "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -50714,26 +50849,31 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", - "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/string.prototype.trimstart": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", - "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -52349,9 +52489,10 @@ } }, "node_modules/typed-array-length": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.5.tgz", - "integrity": "sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", @@ -55458,11 +55599,11 @@ "@rollup/plugin-terser": "0.4.4", "@rollup/plugin-typescript": "^11.0.0", "@types/node": "20.14.12", - "@types/react": "18.0.17", - "@types/react-dom": "18.0.6", + "@types/react": "18.3.3", + "@types/react-dom": "18.3.0", "ncp": "2.0.0", - "react": "18.2.0", - "react-dom": "18.2.0", + "react": "18.3.1", + "react-dom": "18.3.1", "rollup": "3.29.4", "rollup-plugin-polyfill-node": "^0.13.0" }, @@ -57504,18 +57645,6 @@ "purgecss": "bin/purgecss.js" } }, - "packages/atomic/node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "dev": true, - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "packages/atomic/node_modules/rollup": { "version": "4.18.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz", @@ -58638,13 +58767,13 @@ }, "devDependencies": { "@coveo/release": "1.0.0", - "@testing-library/react": "14.3.0", + "@testing-library/react": "14.3.1", "@types/jest": "29.5.12", - "@types/react": "18.2.21", - "@types/react-dom": "18.2.7", + "@types/react": "18.3.3", + "@types/react-dom": "18.3.0", "@typescript-eslint/eslint-plugin": "7.17.0", "eslint-plugin-jest-dom": "5.4.0", - "eslint-plugin-react": "7.33.2", + "eslint-plugin-react": "7.35.0", "eslint-plugin-testing-library": "6.2.2", "gts": "5.3.1", "jest": "29.7.0", @@ -60510,13 +60639,13 @@ "@coveo/atomic-react": "2.12.2", "@coveo/headless": "2.74.0", "next": "14.2.5", - "react": "18.2.0", - "react-dom": "18.2.0" + "react": "18.3.1", + "react-dom": "18.3.1" }, "devDependencies": { "@types/node": "20.14.12", - "@types/react": "18.0.17", - "@types/react-dom": "18.0.6", + "@types/react": "18.3.3", + "@types/react-dom": "18.3.0", "cypress": "13.13.1", "cypress-repeat": "2.3.5", "ncp": "2.0.0", @@ -60790,17 +60919,17 @@ "@coveo/atomic": "2.74.0", "@coveo/atomic-react": "2.12.2", "@coveo/headless": "2.74.0", - "react": "18.2.0", - "react-dom": "18.2.0" + "react": "18.3.1", + "react-dom": "18.3.1" }, "devDependencies": { - "@types/react": "18.0.17", - "@types/react-dom": "18.0.6", + "@types/react": "18.3.3", + "@types/react-dom": "18.3.0", "@typescript-eslint/eslint-plugin": "^7.0.0", "css-loader": "7.1.0", "cypress": "13.13.1", "cypress-repeat": "2.3.5", - "eslint-plugin-react": "7.30.1", + "eslint-plugin-react": "7.35.0", "gts": "5.3.1", "ncp": "2.0.0", "style-loader": "4.0.0", @@ -61031,15 +61160,20 @@ "name": "@coveo/headless-commerce-react-samples", "version": "0.1.0", "dependencies": { - "@coveo/headless": "^2.73.0", - "@testing-library/jest-dom": "^6.4.5", - "@testing-library/react": "^13.4.0", - "@testing-library/user-event": "^13.5.0", - "react": "^18.2.0", - "react-dom": "^18.2.0", + "@coveo/headless": "2.74.0", + "@testing-library/jest-dom": "6.4.8", + "@testing-library/react": "16.0.0", + "@testing-library/user-event": "14.5.2", + "react": "18.3.1", + "react-dom": "18.3.1", "react-scripts": "5.0.1" } }, + "packages/samples/headless-commerce-react/node_modules/@adobe/css-tools": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.0.tgz", + "integrity": "sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ==" + }, "packages/samples/headless-commerce-react/node_modules/@jest/console": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.5.1.tgz", @@ -61420,27 +61554,29 @@ } }, "packages/samples/headless-commerce-react/node_modules/@testing-library/dom": { - "version": "8.20.1", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.1.tgz", - "integrity": "sha512-/DiOQ5xBxgdYRC8LNk7U+RWat0S3qRLeIw3ZIkMQ9kkVlRmwD/Eg8k8CqIpD6GW7u20JIUOfMKbxtiLutpjQ4g==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", + "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", + "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", - "aria-query": "5.1.3", + "aria-query": "5.3.0", "chalk": "^4.1.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", "pretty-format": "^27.0.2" }, "engines": { - "node": ">=12" + "node": ">=18" } }, "packages/samples/headless-commerce-react/node_modules/@testing-library/dom/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -61453,41 +61589,54 @@ } }, "packages/samples/headless-commerce-react/node_modules/@testing-library/jest-dom": { - "version": "5.17.0", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.17.0.tgz", - "integrity": "sha512-ynmNeT7asXyH3aSVv4vvX4Rb+0qjOhdNHnO/3vuZNqPmhDpV/+rCSGwQ7bLcmU2cJ4dvoheIO85LQj0IbJHEtg==", + "version": "6.4.8", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.4.8.tgz", + "integrity": "sha512-JD0G+Zc38f5MBHA4NgxQMR5XtO5Jx9g86jqturNTt2WUfRmLDIY7iKkWHDCCTiDuFMre6nxAD5wHw9W5kI4rGw==", "dependencies": { - "@adobe/css-tools": "^4.0.1", + "@adobe/css-tools": "^4.4.0", "@babel/runtime": "^7.9.2", - "@types/testing-library__jest-dom": "^5.9.1", "aria-query": "^5.0.0", "chalk": "^3.0.0", "css.escape": "^1.5.1", - "dom-accessibility-api": "^0.5.6", - "lodash": "^4.17.15", + "dom-accessibility-api": "^0.6.3", + "lodash": "^4.17.21", "redent": "^3.0.0" }, "engines": { - "node": ">=8", + "node": ">=14", "npm": ">=6", "yarn": ">=1" } }, + "packages/samples/headless-commerce-react/node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==" + }, "packages/samples/headless-commerce-react/node_modules/@testing-library/react": { - "version": "13.4.0", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-13.4.0.tgz", - "integrity": "sha512-sXOGON+WNTh3MLE9rve97ftaZukN3oNf2KjDy7YTx6hcTO2uuLHuCGynMDhFwGw/jYf4OJ2Qk0i4i79qMNNkyw==", + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.0.0.tgz", + "integrity": "sha512-guuxUKRWQ+FgNX0h0NS0FIq3Q3uLtWVpBzcLOggmfMoUpgBnzBzvLLd4fbm6yS8ydJd94cIfY4yP9qUQjM2KwQ==", "dependencies": { - "@babel/runtime": "^7.12.5", - "@testing-library/dom": "^8.5.0", - "@types/react-dom": "^18.0.0" + "@babel/runtime": "^7.12.5" }, "engines": { - "node": ">=12" + "node": ">=18" }, "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0", + "@types/react-dom": "^18.0.0", "react": "^18.0.0", "react-dom": "^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } } }, "packages/samples/headless-commerce-react/node_modules/@testing-library/user-event": { @@ -61563,6 +61712,14 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "packages/samples/headless-commerce-react/node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dependencies": { + "dequal": "^2.0.3" + } + }, "packages/samples/headless-commerce-react/node_modules/babel-jest": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz", @@ -63473,22 +63630,22 @@ "@coveo/auth": "1.11.21", "@coveo/headless": "2.74.0", "@testing-library/jest-dom": "6.4.8", - "@testing-library/react": "14.3.0", + "@testing-library/react": "14.3.1", "@testing-library/user-event": "14.5.2", "@types/escape-html": "1.0.4", "@types/express": "4.17.21", "@types/node": "20.14.12", - "@types/react": "18.0.17", - "@types/react-dom": "18.0.6", + "@types/react": "18.3.3", + "@types/react-dom": "18.3.0", "@types/react-router-dom": "5.3.3", "dayjs": "1.11.12", "escape-html": "1.0.3", "express": "4.19.2", "filesize": "10.1.4", "jest-watch-typeahead": "2.2.2", - "react": "18.2.0", - "react-dom": "18.2.0", - "react-router-dom": "6.16.0", + "react": "18.3.1", + "react-dom": "18.3.1", + "react-router-dom": "6.25.1", "react-scripts": "5.0.1", "typescript": "5.4.5", "web-vitals": "3.5.2" @@ -66907,13 +67064,13 @@ "@coveo/headless": "2.74.0", "@coveo/headless-react": "1.0.20", "next": "14.2.5", - "react": "18.2.0", - "react-dom": "18.2.0" + "react": "18.3.1", + "react-dom": "18.3.1" }, "devDependencies": { "@types/node": "20.14.12", - "@types/react": "18.2.21", - "@types/react-dom": "18.2.7", + "@types/react": "18.3.3", + "@types/react-dom": "18.3.0", "cypress": "13.13.1", "cypress-repeat": "2.3.5", "cypress-web-vitals": "4.1.2", @@ -66931,13 +67088,13 @@ "dependencies": { "@coveo/headless-ssr-samples-common": "0.0.0", "next": "14.2.5", - "react": "18.2.0", - "react-dom": "18.2.0" + "react": "18.3.1", + "react-dom": "18.3.1" }, "devDependencies": { "@types/node": "20.14.12", - "@types/react": "18.2.21", - "@types/react-dom": "18.2.7", + "@types/react": "18.3.3", + "@types/react-dom": "18.3.0", "eslint": "8.57.0", "eslint-config-next": "14.2.5", "typescript": "5.4.5" @@ -67401,13 +67558,13 @@ "dependencies": { "@coveo/headless-ssr-samples-common": "0.0.0", "next": "14.2.5", - "react": "18.2.0", - "react-dom": "18.2.0" + "react": "18.3.1", + "react-dom": "18.3.1" }, "devDependencies": { "@types/node": "20.14.12", - "@types/react": "18.2.21", - "@types/react-dom": "18.2.7", + "@types/react": "18.3.3", + "@types/react-dom": "18.3.0", "eslint": "8.57.0", "eslint-config-next": "14.2.5", "typescript": "5.4.5" @@ -67422,8 +67579,8 @@ "@coveo/atomic-hosted-page": "0.6.1", "@coveo/atomic-react": "2.12.2", "@coveo/headless": "2.74.0", - "react": "18.2.0", - "react-dom": "18.2.0" + "react": "18.3.1", + "react-dom": "18.3.1" }, "devDependencies": { "cypress": "13.13.1", diff --git a/package.json b/package.json index c84daf1222a..ddec47b8c0a 100644 --- a/package.json +++ b/package.json @@ -81,8 +81,8 @@ "nx": "19.0.4", "patch-package": "8.0.0", "prettier": "3.3.3", - "react": "18.2.0", - "react-dom": "18.2.0", + "react": "18.3.1", + "react-dom": "18.3.1", "react-syntax-highlighter": "15.5.0", "rimraf": "5.0.9", "semver": "7.6.3", diff --git a/packages/atomic-react/package.json b/packages/atomic-react/package.json index 51c56ffd2e5..8c1531f2105 100644 --- a/packages/atomic-react/package.json +++ b/packages/atomic-react/package.json @@ -39,11 +39,11 @@ "@rollup/plugin-replace": "^5.0.0", "@rollup/plugin-typescript": "^11.0.0", "@types/node": "20.14.12", - "@types/react": "18.0.17", - "@types/react-dom": "18.0.6", + "@types/react": "18.3.3", + "@types/react-dom": "18.3.0", "ncp": "2.0.0", - "react": "18.2.0", - "react-dom": "18.2.0", + "react": "18.3.1", + "react-dom": "18.3.1", "rollup": "3.29.4", "rollup-plugin-polyfill-node": "^0.13.0", "@rollup/plugin-terser": "0.4.4" diff --git a/packages/headless-react/package.json b/packages/headless-react/package.json index 98234332cee..139162bb632 100644 --- a/packages/headless-react/package.json +++ b/packages/headless-react/package.json @@ -37,13 +37,13 @@ }, "devDependencies": { "@coveo/release": "1.0.0", - "@testing-library/react": "14.3.0", + "@testing-library/react": "14.3.1", "@types/jest": "29.5.12", - "@types/react": "18.2.21", - "@types/react-dom": "18.2.7", + "@types/react": "18.3.3", + "@types/react-dom": "18.3.0", "@typescript-eslint/eslint-plugin": "7.17.0", "eslint-plugin-jest-dom": "5.4.0", - "eslint-plugin-react": "7.33.2", + "eslint-plugin-react": "7.35.0", "eslint-plugin-testing-library": "6.2.2", "gts": "5.3.1", "jest": "29.7.0", diff --git a/packages/samples/atomic-next/package.json b/packages/samples/atomic-next/package.json index e0fb7fb275d..71ff14a4c01 100644 --- a/packages/samples/atomic-next/package.json +++ b/packages/samples/atomic-next/package.json @@ -7,13 +7,13 @@ "@coveo/atomic-react": "2.12.2", "@coveo/headless": "2.74.0", "next": "14.2.5", - "react": "18.2.0", - "react-dom": "18.2.0" + "react": "18.3.1", + "react-dom": "18.3.1" }, "devDependencies": { "@types/node": "20.14.12", - "@types/react": "18.0.17", - "@types/react-dom": "18.0.6", + "@types/react": "18.3.3", + "@types/react-dom": "18.3.0", "cypress": "13.13.1", "cypress-repeat": "2.3.5", "ncp": "2.0.0", diff --git a/packages/samples/atomic-react/package.json b/packages/samples/atomic-react/package.json index 152c78a3211..206fbeaa12f 100644 --- a/packages/samples/atomic-react/package.json +++ b/packages/samples/atomic-react/package.json @@ -7,17 +7,17 @@ "@coveo/atomic": "2.74.0", "@coveo/atomic-react": "2.12.2", "@coveo/headless": "2.74.0", - "react": "18.2.0", - "react-dom": "18.2.0" + "react": "18.3.1", + "react-dom": "18.3.1" }, "devDependencies": { - "@types/react": "18.0.17", - "@types/react-dom": "18.0.6", + "@types/react": "18.3.3", + "@types/react-dom": "18.3.0", "@typescript-eslint/eslint-plugin": "^7.0.0", "css-loader": "7.1.0", "cypress": "13.13.1", "cypress-repeat": "2.3.5", - "eslint-plugin-react": "7.30.1", + "eslint-plugin-react": "7.35.0", "gts": "5.3.1", "ncp": "2.0.0", "style-loader": "4.0.0", diff --git a/packages/samples/headless-commerce-react/package.json b/packages/samples/headless-commerce-react/package.json index 4f5982f2bd9..7a592c3a506 100644 --- a/packages/samples/headless-commerce-react/package.json +++ b/packages/samples/headless-commerce-react/package.json @@ -3,12 +3,12 @@ "version": "0.1.0", "private": true, "dependencies": { - "@coveo/headless": "^2.73.0", - "@testing-library/jest-dom": "^6.4.5", - "@testing-library/react": "^13.4.0", - "@testing-library/user-event": "^13.5.0", - "react": "^18.2.0", - "react-dom": "^18.2.0", + "@coveo/headless": "^2.74.0", + "@testing-library/jest-dom": "6.4.8", + "@testing-library/react": "16.0.0", + "@testing-library/user-event": "14.5.2", + "react": "18.3.1", + "react-dom": "18.3.1", "react-scripts": "5.0.1" }, "scripts": { diff --git a/packages/samples/headless-react/package.json b/packages/samples/headless-react/package.json index 8bb0633aca2..55fc24eefe9 100644 --- a/packages/samples/headless-react/package.json +++ b/packages/samples/headless-react/package.json @@ -7,22 +7,22 @@ "@coveo/auth": "1.11.21", "@coveo/headless": "2.74.0", "@testing-library/jest-dom": "6.4.8", - "@testing-library/react": "14.3.0", + "@testing-library/react": "14.3.1", "@testing-library/user-event": "14.5.2", "@types/escape-html": "1.0.4", "@types/express": "4.17.21", "@types/node": "20.14.12", - "@types/react": "18.0.17", - "@types/react-dom": "18.0.6", + "@types/react": "18.3.3", + "@types/react-dom": "18.3.0", "@types/react-router-dom": "5.3.3", "dayjs": "1.11.12", "escape-html": "1.0.3", "express": "4.19.2", "filesize": "10.1.4", "jest-watch-typeahead": "2.2.2", - "react": "18.2.0", - "react-dom": "18.2.0", - "react-router-dom": "6.16.0", + "react": "18.3.1", + "react-dom": "18.3.1", + "react-router-dom": "6.25.1", "react-scripts": "5.0.1", "typescript": "5.4.5", "web-vitals": "3.5.2" diff --git a/packages/samples/headless-ssr/app-router/package.json b/packages/samples/headless-ssr/app-router/package.json index 2fa8606e1f2..8b4da1ca982 100644 --- a/packages/samples/headless-ssr/app-router/package.json +++ b/packages/samples/headless-ssr/app-router/package.json @@ -15,13 +15,13 @@ "dependencies": { "@coveo/headless-ssr-samples-common": "0.0.0", "next": "14.2.5", - "react": "18.2.0", - "react-dom": "18.2.0" + "react": "18.3.1", + "react-dom": "18.3.1" }, "devDependencies": { "@types/node": "20.14.12", - "@types/react": "18.2.21", - "@types/react-dom": "18.2.7", + "@types/react": "18.3.3", + "@types/react-dom": "18.3.0", "eslint": "8.57.0", "eslint-config-next": "14.2.5", "typescript": "5.4.5" diff --git a/packages/samples/headless-ssr/package.json b/packages/samples/headless-ssr/package.json index f02d22f06de..765b12ead40 100644 --- a/packages/samples/headless-ssr/package.json +++ b/packages/samples/headless-ssr/package.json @@ -11,13 +11,13 @@ "@coveo/headless-react": "1.0.20", "@coveo/headless": "2.74.0", "next": "14.2.5", - "react": "18.2.0", - "react-dom": "18.2.0" + "react": "18.3.1", + "react-dom": "18.3.1" }, "devDependencies": { "@types/node": "20.14.12", - "@types/react": "18.2.21", - "@types/react-dom": "18.2.7", + "@types/react": "18.3.3", + "@types/react-dom": "18.3.0", "eslint": "8.57.0", "eslint-config-react-app": "7.0.1", "typescript": "5.4.5", diff --git a/packages/samples/headless-ssr/pages-router/package.json b/packages/samples/headless-ssr/pages-router/package.json index c7cb286a9db..098902e0d87 100644 --- a/packages/samples/headless-ssr/pages-router/package.json +++ b/packages/samples/headless-ssr/pages-router/package.json @@ -15,13 +15,13 @@ "dependencies": { "@coveo/headless-ssr-samples-common": "0.0.0", "next": "14.2.5", - "react": "18.2.0", - "react-dom": "18.2.0" + "react": "18.3.1", + "react-dom": "18.3.1" }, "devDependencies": { "@types/node": "20.14.12", - "@types/react": "18.2.21", - "@types/react-dom": "18.2.7", + "@types/react": "18.3.3", + "@types/react-dom": "18.3.0", "eslint": "8.57.0", "eslint-config-next": "14.2.5", "typescript": "5.4.5" diff --git a/packages/samples/iife/package.json b/packages/samples/iife/package.json index 2ceef08468f..631fc0d209b 100644 --- a/packages/samples/iife/package.json +++ b/packages/samples/iife/package.json @@ -16,8 +16,8 @@ "@coveo/atomic-hosted-page": "0.6.1", "@coveo/atomic-react": "2.12.2", "@coveo/headless": "2.74.0", - "react": "18.2.0", - "react-dom": "18.2.0" + "react": "18.3.1", + "react-dom": "18.3.1" }, "devDependencies": { "cypress": "13.13.1", From c474a8d9cb77aeb5b88a992847e5a233adb55123 Mon Sep 17 00:00:00 2001 From: ylakhdar Date: Tue, 30 Jul 2024 10:08:16 -0400 Subject: [PATCH 02/23] feat(headless SSR): define the SSR Commerce Engine (#4198) In this PR: * Defining a new `SSRCommerceEngine` interface supporting two solution types (listing, search) * Implementing a `defineCommerceEngine()` method for commerce engine configuration (to stay consistent with the non-commerce SSR package) > Note: > Only supporting listing page in this PR. > The support for search page will be tackled in KIT-3394 https://coveord.atlassian.net/browse/KIT-3390 --- .../commerce-engine/commerce-engine.ssr.ts | 202 ++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts diff --git a/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts b/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts new file mode 100644 index 00000000000..2f7e55e9283 --- /dev/null +++ b/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts @@ -0,0 +1,202 @@ +/** + * Utility functions to be used for Commerce Server Side Rendering. + */ +import {Action, UnknownAction} from '@reduxjs/toolkit'; +import {stateKey} from '../../app/state-key'; +import {buildProductListing} from '../../controllers/commerce/product-listing/headless-product-listing'; +import type {Controller} from '../../controllers/controller/headless-controller'; +import {createWaitForActionMiddleware} from '../../utils/utils'; +import { + buildControllerDefinitions, + composeFunction, + createStaticState, +} from '../ssr-engine/common'; +import { + ControllerDefinitionsMap, + InferControllerPropsMapFromDefinitions, +} from '../ssr-engine/types/common'; +import { + EngineDefinition, + EngineDefinitionOptions, +} from '../ssr-engine/types/core-engine'; +import { + CommerceEngine, + CommerceEngineOptions, + buildCommerceEngine, +} from './commerce-engine'; + +/** + * The SSR commerce engine. + */ +export interface SSRCommerceEngine extends CommerceEngine { + /** + * Waits for the search to be completed and returns a promise that resolves to a `SearchCompletedAction`. + */ + waitForSearchCompletedAction(): Promise; +} + +export type CommerceEngineDefinitionOptions< + TControllers extends ControllerDefinitionsMap, +> = EngineDefinitionOptions; + +function isListingFetchCompletedAction(action: unknown): action is Action { + return /^commerce\/productListing\/fetch\/(fulfilled|rejected)$/.test( + (action as UnknownAction).type + ); +} + +// TODO: KIT-3394 - Uncomment this function when implementing the search solution type +// function isSearchCompletedAction(action: unknown): action is Action { +// return /^commerce\/search\/executeSearch\/(fulfilled|rejected)$/.test( +// (action as UnknownAction).type +// ); +// } + +function buildSSRCommerceEngine( + options: CommerceEngineOptions +): SSRCommerceEngine { + const {middleware, promise} = createWaitForActionMiddleware( + isListingFetchCompletedAction + // TODO: KIT-3394 - Implement the logic for the search solution type + // isSearchCompletedAction + ); + const commerceEngine = buildCommerceEngine({ + ...options, + middlewares: [...(options.middlewares ?? []), middleware], + }); + return { + ...commerceEngine, + + get [stateKey]() { + return commerceEngine[stateKey]; + }, + + waitForSearchCompletedAction() { + return promise; + }, + }; +} + +export interface CommerceEngineDefinition< + TControllers extends ControllerDefinitionsMap, +> extends EngineDefinition< + SSRCommerceEngine, + TControllers, + CommerceEngineOptions + > {} + +/** + * @internal + * Initializes a Commerce engine definition in SSR with given controllers definitions and commerce engine config. + * @param options - The commerce engine definition + * @returns Three utility functions to fetch the initial state of the engine in SSR, hydrate the state in CSR, + * and a build function that can be used for edge cases requiring more control. + */ +export function defineCommerceEngine< + TControllerDefinitions extends ControllerDefinitionsMap< + CommerceEngine, + Controller + >, +>( + options: CommerceEngineDefinitionOptions +): CommerceEngineDefinition { + const {controllers: controllerDefinitions, ...engineOptions} = options; + type Definition = CommerceEngineDefinition; + type BuildFunction = Definition['build']; + type FetchStaticStateFunction = Definition['fetchStaticState']; + type HydrateStaticStateFunction = Definition['hydrateStaticState']; + type FetchStaticStateFromBuildResultFunction = + FetchStaticStateFunction['fromBuildResult']; + type HydrateStaticStateFromBuildResultFunction = + HydrateStaticStateFunction['fromBuildResult']; + type BuildParameters = Parameters; + type FetchStaticStateParameters = Parameters; + type HydrateStaticStateParameters = Parameters; + type FetchStaticStateFromBuildResultParameters = + Parameters; + type HydrateStaticStateFromBuildResultParameters = + Parameters; + + const getOpts = () => { + return engineOptions; + }; + + const build: BuildFunction = async (...[buildOptions]: BuildParameters) => { + const engine = buildSSRCommerceEngine( + buildOptions?.extend ? await buildOptions.extend(getOpts()) : getOpts() + ); + const controllers = buildControllerDefinitions({ + definitionsMap: (controllerDefinitions ?? {}) as TControllerDefinitions, + engine, + propsMap: (buildOptions && 'controllers' in buildOptions + ? buildOptions.controllers + : {}) as InferControllerPropsMapFromDefinitions, + }); + return { + engine, + controllers, + }; + }; + + const fetchStaticState: FetchStaticStateFunction = composeFunction( + async (...params: FetchStaticStateParameters) => { + const buildResult = await build(...params); + const staticState = await fetchStaticState.fromBuildResult({ + buildResult, + }); + return staticState; + }, + { + fromBuildResult: async ( + ...params: FetchStaticStateFromBuildResultParameters + ) => { + const [ + { + buildResult: {engine, controllers}, + }, + ] = params; + + buildProductListing(engine).executeFirstRequest(); + // TODO: KIT-3394 - Implement the logic for the search solution type + // buildSearch(engine).executeFirstSearch(); + + return createStaticState({ + searchAction: await engine.waitForSearchCompletedAction(), + controllers, + }); + }, + } + ); + + const hydrateStaticState: HydrateStaticStateFunction = composeFunction( + async (...params: HydrateStaticStateParameters) => { + const buildResult = await build(...(params as BuildParameters)); + const staticState = await hydrateStaticState.fromBuildResult({ + buildResult, + searchAction: params[0]!.searchAction, + }); + return staticState; + }, + { + fromBuildResult: async ( + ...params: HydrateStaticStateFromBuildResultParameters + ) => { + const [ + { + buildResult: {engine, controllers}, + searchAction, + }, + ] = params; + engine.dispatch(searchAction); + await engine.waitForSearchCompletedAction(); + return {engine, controllers}; + }, + } + ); + + return { + build, + fetchStaticState, + hydrateStaticState, + }; +} From 9f795cc48fecbe9ab81dce12a9ed1f0712488325 Mon Sep 17 00:00:00 2001 From: Alex Prudhomme <78121423+alexprudhomme@users.noreply.github.com> Date: Tue, 30 Jul 2024 11:10:54 -0400 Subject: [PATCH 03/23] test(commerce): fix flaky commerce query summary tests (#4235) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://coveord.atlassian.net/browse/KIT-3369 fixes this strict selector error : Retry #1 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect.toBeVisible: Error: strict mode violation: getByText(/^Product 1 of 1 for kayak$/) resolved to 2 elements: 1)
Product 1 of 1 for kayak
aka locator('#aria-live-eypty-query-summary') 2)
…
aka locator('#code-root').getByText('Product 1 of 1 for kayak') --- .../commerce/atomic-commerce-query-summary/e2e/page-object.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/atomic/src/components/commerce/atomic-commerce-query-summary/e2e/page-object.ts b/packages/atomic/src/components/commerce/atomic-commerce-query-summary/e2e/page-object.ts index 17956c5a1d9..0ecf00be447 100644 --- a/packages/atomic/src/components/commerce/atomic-commerce-query-summary/e2e/page-object.ts +++ b/packages/atomic/src/components/commerce/atomic-commerce-query-summary/e2e/page-object.ts @@ -15,7 +15,9 @@ export class QuerySummaryPageObject extends BasePageObject<'atomic-commerce-quer } text(summaryRegex: RegExp) { - return this.page.getByText(summaryRegex); + return this.page + .locator(':not([role="status"])') + .filter({hasText: summaryRegex}); } get container() { From 792ad91d4f4398d99f397b890f5504a6b52914ed Mon Sep 17 00:00:00 2001 From: ylakhdar Date: Tue, 30 Jul 2024 13:02:29 -0400 Subject: [PATCH 04/23] chore(headless SSR): create commerce SSR sub-package (#4226) Add boilerplate to for the ssr commerce samples https://coveord.atlassian.net/browse/KIT-3398 --- package-lock.json | 2 +- .../config/api-extractor/ssr.commerce.json | 8 + packages/headless/esbuild.mjs | 2 + packages/headless/package.json | 1 + packages/headless/src/ssr-commerce.index.ts | 154 ++++++++++++++++++ packages/headless/ssr-commerce/package.json | 10 ++ 6 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 packages/headless/config/api-extractor/ssr.commerce.json create mode 100644 packages/headless/src/ssr-commerce.index.ts create mode 100644 packages/headless/ssr-commerce/package.json diff --git a/package-lock.json b/package-lock.json index 4a8f6cb5b22..b045afc8d47 100644 --- a/package-lock.json +++ b/package-lock.json @@ -61160,7 +61160,7 @@ "name": "@coveo/headless-commerce-react-samples", "version": "0.1.0", "dependencies": { - "@coveo/headless": "2.74.0", + "@coveo/headless": "^2.74.0", "@testing-library/jest-dom": "6.4.8", "@testing-library/react": "16.0.0", "@testing-library/user-event": "14.5.2", diff --git a/packages/headless/config/api-extractor/ssr.commerce.json b/packages/headless/config/api-extractor/ssr.commerce.json new file mode 100644 index 00000000000..d15a3d5616f --- /dev/null +++ b/packages/headless/config/api-extractor/ssr.commerce.json @@ -0,0 +1,8 @@ +{ + "extends": "./base.json", + "mainEntryPointFilePath": "/dist/definitions/ssr-commerce.index.d.ts", + "docModel": { + "enabled": true, + "apiJsonFilePath": "/temp/ssr-commerce.api.json" + } +} diff --git a/packages/headless/esbuild.mjs b/packages/headless/esbuild.mjs index 2561aa1dc70..0db3fbab006 100644 --- a/packages/headless/esbuild.mjs +++ b/packages/headless/esbuild.mjs @@ -17,6 +17,7 @@ const useCaseEntries = { 'case-assist': 'src/case-assist.index.ts', insight: 'src/insight.index.ts', ssr: 'src/ssr.index.ts', + 'ssr-commerce': 'src/ssr-commerce.index.ts', commerce: 'src/commerce.index.ts', }; @@ -36,6 +37,7 @@ function getUmdGlobalName(useCase) { 'case-assist': 'CoveoHeadlessCaseAssist', insight: 'CoveoHeadlessInsight', ssr: 'CoveoHeadlessSSR', + 'ssr-commerce': 'CoveoHeadlessCommerceSSR', commerce: 'CoveoHeadlessCommerce', }; diff --git a/packages/headless/package.json b/packages/headless/package.json index 6301785c0c3..206c9b3a672 100644 --- a/packages/headless/package.json +++ b/packages/headless/package.json @@ -23,6 +23,7 @@ "insight/", "case-assist/", "ssr/", + "ssr-commerce/", "commerce/" ], "scripts": { diff --git a/packages/headless/src/ssr-commerce.index.ts b/packages/headless/src/ssr-commerce.index.ts new file mode 100644 index 00000000000..78594b4ee68 --- /dev/null +++ b/packages/headless/src/ssr-commerce.index.ts @@ -0,0 +1,154 @@ +export type {Unsubscribe, Middleware} from '@reduxjs/toolkit'; +export {createAction, createAsyncThunk, createReducer} from '@reduxjs/toolkit'; +export type {Relay} from '@coveo/relay'; + +// Main App +export type {CommerceEngineOptions} from './app/commerce-engine/commerce-engine'; +export type {CommerceEngineConfiguration} from './app/commerce-engine/commerce-engine-configuration'; +export type { + SSRCommerceEngine as CommerceEngine, + CommerceEngineDefinition, + CommerceEngineDefinitionOptions, +} from './app/commerce-engine/commerce-engine.ssr'; +export {defineCommerceEngine} from './app/commerce-engine/commerce-engine.ssr'; +export {getSampleCommerceEngineConfiguration} from './app/commerce-engine/commerce-engine-configuration'; + +// export type +export type {CoreEngineNext, ExternalEngineOptions} from './app/engine'; +export type { + EngineConfiguration, + AnalyticsConfiguration, + AnalyticsRuntimeEnvironment, +} from './app/engine-configuration'; +export type { + ControllerDefinitionWithoutProps, + ControllerDefinitionWithProps, + ControllerDefinitionsMap, + InferControllerFromDefinition, + InferControllersMapFromDefinition, + InferControllerStaticStateFromController, + InferControllerStaticStateMapFromControllers, + InferControllerStaticStateMapFromDefinitions, +} from './app/ssr-engine/types/common'; +export type {Build} from './app/ssr-engine/types/build'; +export type { + EngineDefinition, + InferStaticState, + InferHydratedState, + InferBuildResult, +} from './app/ssr-engine/types/core-engine'; +export type {LoggerOptions} from './app/logger'; + +export type {LogLevel} from './app/logger'; + +// State +export type { + CommerceSearchParametersState, + CommerceProductListingParametersState, + CommerceAppState, +} from './state/commerce-app-state'; + +//#region Controllers +export type { + Controller, + Subscribable, +} from './controllers/controller/headless-controller'; + +export type {CategoryFacet} from './controllers/commerce/core/facets/category/headless-commerce-category-facet'; +export type { + DateFacet, + DateFacetState, +} from './controllers/commerce/core/facets/date/headless-commerce-date-facet'; +export type {RegularFacetValue} from './controllers/commerce/core/facets/headless-core-commerce-facet'; +export type { + NumericFacet, + NumericFacetState, +} from './controllers/commerce/core/facets/numeric/headless-commerce-numeric-facet'; +export type {RegularFacet} from './controllers/commerce/core/facets/regular/headless-commerce-regular-facet'; + +// TODO: KIT-3391 - export other SSR commerce controllers + +//#endregion + +//#region Grouped actions +export * from './features/commerce/context/context-actions-loader'; +export * from './features/commerce/search/search-actions-loader'; +export * from './features/commerce/product-listing/product-listing-actions-loader'; +export * from './features/commerce/recommendations/recommendations-actions-loader'; +export * from './features/commerce/pagination/pagination-actions-loader'; +export * from './features/commerce/product/product-actions-loaders'; +export * from './features/commerce/context/cart/cart-actions-loader'; +export * from './features/commerce/sort/sort-actions-loader'; +export * from './features/commerce/facets/core-facet/core-facet-actions-loader'; +export * from './features/commerce/facets/category-facet/category-facet-actions-loader'; +export * from './features/commerce/facets/regular-facet/regular-facet-actions-loader'; +export * from './features/commerce/facets/date-facet/date-facet-actions-loader'; +export * from './features/commerce/facets/numeric-facet/numeric-facet-actions-loader'; +export * from './features/commerce/query-set/query-set-actions-loader'; +export * from './features/commerce/query-suggest/query-suggest-actions-loader'; +export * from './features/commerce/configuration/configuration-actions-loader'; +export * from './features/commerce/query/query-actions-loader'; +export * from './features/commerce/search-parameters/search-parameters-actions-loader'; +export * from './features/commerce/product-listing-parameters/product-listing-parameters-actions-loader'; +export * from './features/commerce/triggers/triggers-actions-loader'; +export * from './features/commerce/instant-products/instant-products-actions-loader'; +export * from './features/commerce/recent-queries/recent-queries-actions-loader'; +export * from './features/commerce/standalone-search-box-set/standalone-search-box-set-actions-loader'; +export {buildResultTemplatesManager} from './features/result-templates/result-templates-manager'; +//#endregion + +// Types & Helpers +export {buildSSRSearchParameterSerializer} from './features/search-parameters/search-parameter-serializer.ssr'; +export type { + BaseProduct, + Product, + ChildProduct, +} from './api/commerce/common/product'; +export type { + SortCriterion, + SortByDate, + SortByField, + SortByNoSort, + SortByQRE, + SortByRelevancy, +} from './features/sort-criteria/criteria'; +export { + SortBy, + SortOrder, + buildDateSortCriterion, + buildCriterionExpression, + buildFieldSortCriterion, + buildNoSortCriterion, + buildQueryRankingExpressionSortCriterion, + buildRelevanceSortCriterion, +} from './features/sort-criteria/criteria'; +export {parseCriterionExpression} from './features/sort-criteria/criteria-parser'; +export type {Template} from './features/templates/templates-manager.ts'; +export type { + ProductTemplate, + ProductTemplateCondition, + ProductTemplatesManager, +} from './features/commerce/product-templates/product-templates-manager'; +export {ProductTemplatesHelpers} from './features/commerce/product-templates/product-templates-helpers'; + +export { + platformUrl, + analyticsUrl, + getOrganizationEndpoints, +} from './api/platform-client'; +export type {PlatformEnvironment} from './utils/url-utils'; + +export {buildSearchParameterSerializer} from './features/search-parameters/search-parameter-serializer'; +export type {HighlightKeyword} from './utils/highlight'; +export {VERSION} from './utils/version'; +export type { + RelativeDate, + RelativeDatePeriod, + RelativeDateUnit, +} from './api/search/date/relative-date'; +export { + deserializeRelativeDate, + validateRelativeDate, +} from './api/search/date/relative-date'; + +export * from './utils/query-expression/query-expression'; diff --git a/packages/headless/ssr-commerce/package.json b/packages/headless/ssr-commerce/package.json new file mode 100644 index 00000000000..1b997c60fb2 --- /dev/null +++ b/packages/headless/ssr-commerce/package.json @@ -0,0 +1,10 @@ +{ + "private": true, + "name": "ssr-commerce", + "description": "Server side rendering utilities for commerce", + "main": "../dist/ssr-commerce/headless.js", + "module": "../dist/ssr-commerce/headless.esm.js", + "browser": "../dist/browser/ssr-commerce/headless.esm.js", + "types": "../dist/definitions/ssr-commerce.index.d.ts", + "license": "Apache-2.0" +} From 5fc7c95d781650dd75f999698ba82e6d15e039cd Mon Sep 17 00:00:00 2001 From: Felix Perron-Brault Date: Tue, 30 Jul 2024 17:13:02 -0400 Subject: [PATCH 05/23] test(atomic): add tests for atomic-commerce-breadbox (#4179) This PR adds tests for atomic-commerce-breadbox https://coveord.atlassian.net/browse/KIT-3378 --------- Co-authored-by: Frederic Beaudoin Co-authored-by: Louis Bompart --- .../atomic-commerce-breadbox.new.stories.tsx | 53 +-- .../atomic-commerce-breadbox.tsx | 22 +- .../e2e/atomic-commerce-breadbox.e2e.ts | 340 ++++++++++++++++++ .../atomic-commerce-breadbox/e2e/fixture.ts | 18 + .../e2e/page-object.ts | 50 +++ .../commerce-interface-wrapper.tsx | 4 +- 6 files changed, 459 insertions(+), 28 deletions(-) create mode 100644 packages/atomic/src/components/commerce/atomic-commerce-breadbox/e2e/atomic-commerce-breadbox.e2e.ts create mode 100644 packages/atomic/src/components/commerce/atomic-commerce-breadbox/e2e/fixture.ts create mode 100644 packages/atomic/src/components/commerce/atomic-commerce-breadbox/e2e/page-object.ts diff --git a/packages/atomic/src/components/commerce/atomic-commerce-breadbox/atomic-commerce-breadbox.new.stories.tsx b/packages/atomic/src/components/commerce/atomic-commerce-breadbox/atomic-commerce-breadbox.new.stories.tsx index 3b2faa16976..6dc93bdf290 100644 --- a/packages/atomic/src/components/commerce/atomic-commerce-breadbox/atomic-commerce-breadbox.new.stories.tsx +++ b/packages/atomic/src/components/commerce/atomic-commerce-breadbox/atomic-commerce-breadbox.new.stories.tsx @@ -4,15 +4,39 @@ import { } from '@coveo/atomic/storybookUtils/commerce-interface-wrapper'; import {parameters} from '@coveo/atomic/storybookUtils/common-meta-parameters'; import {renderComponent} from '@coveo/atomic/storybookUtils/render-component'; -import {userEvent, waitFor, expect} from '@storybook/test'; +import { + CommerceEngineConfiguration, + getSampleCommerceEngineConfiguration, +} from '@coveo/headless/commerce'; import type {Meta, StoryObj as Story} from '@storybook/web-components'; import {html} from 'lit/static-html.js'; -import {within} from 'shadow-dom-testing-library'; -const {decorator, play} = wrapInCommerceInterface({skipFirstSearch: true}); +const {context, ...restOfConfiguration} = + getSampleCommerceEngineConfiguration(); + +const productListingEngineConfiguration: Partial = + { + context: { + ...context, + country: 'US', + currency: 'USD', + language: 'en', + view: { + url: context.view.url + '/browse/promotions/ui-kit-testing', + }, + }, + ...restOfConfiguration, + }; + +const {decorator, play} = wrapInCommerceInterface({ + engineConfig: productListingEngineConfiguration, + skipFirstSearch: true, + type: 'product-listing', +}); + const meta: Meta = { component: 'atomic-commerce-breadbox', - title: 'Atomic-commerce/Breadbox', + title: 'Atomic-commerce/Interface Components/atomic-commerce-breadbox', id: 'atomic-commerce-breadbox', render: renderComponent, decorators: [decorator], @@ -38,26 +62,5 @@ export const Default: Story = { play: async (context) => { await play(context); await playExecuteFirstSearch(context); - const {canvasElement, step} = context; - const canvas = within(canvasElement); - await step('Wait for the facet values to render', async () => { - await waitFor( - () => expect(canvas.getByShadowTitle('People')).toBeInTheDocument(), - { - timeout: 30e3, - } - ); - }); - await step('Select a facet value', async () => { - const facet = canvas.getByShadowTitle('People'); - await userEvent.click(facet); - await waitFor( - () => - expect( - canvas.getByShadowTitle('Object type: People') - ).toBeInTheDocument(), - {timeout: 30e3} - ); - }); }, }; diff --git a/packages/atomic/src/components/commerce/atomic-commerce-breadbox/atomic-commerce-breadbox.tsx b/packages/atomic/src/components/commerce/atomic-commerce-breadbox/atomic-commerce-breadbox.tsx index d4ab22627fc..09d408cec08 100644 --- a/packages/atomic/src/components/commerce/atomic-commerce-breadbox/atomic-commerce-breadbox.tsx +++ b/packages/atomic/src/components/commerce/atomic-commerce-breadbox/atomic-commerce-breadbox.tsx @@ -12,6 +12,9 @@ import { Breadcrumb, CategoryFacetValue, BreadcrumbValue, + Context, + ContextState, + buildContext, } from '@coveo/headless/commerce'; import {Component, h, State, Element, Prop} from '@stencil/core'; import {FocusTargetController} from '../../../utils/accessibility-utils'; @@ -30,7 +33,10 @@ import {BreadcrumbShowLess} from '../../common/breadbox/breadcrumb-show-less'; import {BreadcrumbShowMore} from '../../common/breadbox/breadcrumb-show-more'; import {Breadcrumb as BreadboxBreadcrumb} from '../../common/breadbox/breadcrumb-types'; import {formatHumanReadable} from '../../common/facets/numeric-facet/formatter'; -import {defaultNumberFormatter} from '../../common/formats/format-common'; +import { + defaultCurrencyFormatter, + defaultNumberFormatter, +} from '../../common/formats/format-common'; import {Hidden} from '../../common/hidden'; import {CommerceBindings} from '../atomic-commerce-interface/atomic-commerce-interface'; @@ -78,6 +84,9 @@ export class AtomicCommerceBreadbox @Element() private host!: HTMLElement; + public context!: Context; + @BindStateToController('context') contextState!: ContextState; + public searchOrListing!: Search | ProductListing; @BindStateToController('breadcrumbManager') @@ -117,6 +126,8 @@ export class AtomicCommerceBreadbox this.searchOrListing = buildSearch(this.bindings.engine); } + this.context = buildContext(this.bindings.engine); + this.breadcrumbManager = this.searchOrListing.breadcrumbManager(); if (window.ResizeObserver) { @@ -237,6 +248,13 @@ export class AtomicCommerceBreadbox ); } + private getNumberFormatter(field: string) { + if (field === 'ec_price' || field === 'ec_promo_price') { + return defaultCurrencyFormatter(this.contextState.currency); + } + return defaultNumberFormatter; + } + private valueForFacetType = ( type: string, field: string, @@ -251,7 +269,7 @@ export class AtomicCommerceBreadbox i18n: this.bindings.i18n, field: field, manualRanges: [], - formatter: defaultNumberFormatter, + formatter: this.getNumberFormatter(field), }), ]; case 'dateRange': diff --git a/packages/atomic/src/components/commerce/atomic-commerce-breadbox/e2e/atomic-commerce-breadbox.e2e.ts b/packages/atomic/src/components/commerce/atomic-commerce-breadbox/e2e/atomic-commerce-breadbox.e2e.ts new file mode 100644 index 00000000000..e30d5eee9d3 --- /dev/null +++ b/packages/atomic/src/components/commerce/atomic-commerce-breadbox/e2e/atomic-commerce-breadbox.e2e.ts @@ -0,0 +1,340 @@ +/* eslint-disable @cspell/spellchecker */ +import {Locator} from '@playwright/test'; +import {test, expect} from './fixture'; + +test.describe('Default', () => { + test.beforeEach(async ({breadbox}) => { + await breadbox.load(); + }); + + test('should be A11y compliant', async ({breadbox, makeAxeBuilder}) => { + await breadbox.getFacetValue('regular').first().click(); + + const accessibilityResults = await makeAxeBuilder().analyze(); + expect(accessibilityResults.violations).toEqual([]); + }); + + test.describe('when restoring the state from URL', () => { + [ + { + facetType: 'regular', + filter: '&f-cat_color=Brown', + breadcrumbLabel: 'Color:Brown', + }, + { + facetType: 'numerical range', + filter: '&nf-ec_price=15..20 ', + breadcrumbLabel: 'Price:$15.00 to $20.00', + }, + { + facetType: 'date range', + filter: '&df-date=2024/05/27@14:32:01..2025/05/27@14:32:01', + breadcrumbLabel: 'Date:2024-05-27 to 2025-05-27', + }, + { + facetType: 'category', + filter: '&cf-ec_category=Accessories', + breadcrumbLabel: 'Category:Accessories', + }, + { + facetType: 'category (nested)', + filter: '&cf-ec_category=Accessories,Surf%20Accessories', + breadcrumbLabel: 'Category:Accessories / Surf Accessories', + }, + ].forEach(({facetType, filter, breadcrumbLabel}) => { + const baseUrl = + 'http://localhost:4400/iframe.html?args=&id=atomic-commerce-breadbox--default&viewMode=story#sortCriteria=relevance'; + + test(`should show the breadcrumb for ${facetType} facet value`, async ({ + page, + breadbox, + }) => { + await page.goto(baseUrl + filter); + await page.waitForURL(baseUrl + filter); + + const breadcrumbButton = breadbox.getBreadcrumbButtons(breadcrumbLabel); + + await expect(breadcrumbButton).toHaveText(breadcrumbLabel); + }); + }); + }); + + test.describe('when a regular facet value is selected', () => { + let firstValueText: string | RegExp; + + test.beforeEach(async ({breadbox}) => { + firstValueText = (await breadbox + .getFacetValue('regular') + .locator('span') + .first() + .textContent()) as string; + await breadbox.getFacetValue('regular', firstValueText).click(); + await breadbox + .getBreadcrumbButtons(firstValueText) + .waitFor({state: 'visible'}); + }); + + test('should disappear when clicking on the breadcrumb button', async ({ + breadbox, + }) => { + const breadcrumbButton = breadbox.getBreadcrumbButtons(firstValueText); + await breadcrumbButton.click(); + + await expect(breadcrumbButton).not.toBeVisible(); + }); + + test('should contain the selected value and the facet name in the breadcrumb button', async ({ + breadbox, + }) => { + const breadcrumbButton = breadbox.getBreadcrumbButtons(firstValueText); + + await expect(breadcrumbButton).toHaveText(`Color:${firstValueText}`); + }); + }); + test.describe('when a category facet value is selected', () => { + let firstValueText: string | RegExp; + test.beforeEach(async ({breadbox}) => { + firstValueText = (await breadbox + .getFacetValue('category') + .first() + .locator('span') + .first() + .textContent()) as string; + await breadbox.getFacetValue('category', firstValueText).click(); + await breadbox + .getBreadcrumbButtons(firstValueText) + .waitFor({state: 'visible'}); + }); + + test('should disappear when clicking on the breadcrumb button', async ({ + breadbox, + }) => { + const breadcrumbButton = breadbox.getBreadcrumbButtons(firstValueText); + await breadcrumbButton.click(); + + await expect(breadcrumbButton).not.toBeVisible(); + }); + + test('should contain the selected value and the facet name in the breadcrumb button', async ({ + breadbox, + }) => { + const breadcrumbButton = breadbox.getBreadcrumbButtons(firstValueText); + + await expect(breadcrumbButton).toHaveText(`Category:${firstValueText}`); + }); + test.describe('when a nested category facet value is selected', () => { + let breadcrumbText: string | RegExp; + + test.beforeEach(async ({breadbox}) => { + breadcrumbText = + (await breadbox + .getFacetValue('category') + .first() + .locator('li span') + .first() + .textContent()) + + ' / ' + + (await breadbox + .getFacetValue('nestedCategory') + .first() + .locator('span') + .first() + .textContent()); + await breadbox.getFacetValue('nestedCategory').first().click(); + await breadbox + .getBreadcrumbButtons() + .first() + .waitFor({state: 'visible'}); + }); + + test('should disappear when clicking on the breadcrumb button', async ({ + breadbox, + }) => { + const breadcrumbButton = breadbox.getBreadcrumbButtons().first(); + await breadcrumbButton.click(); + + await expect(breadcrumbButton).not.toBeVisible(); + }); + + test('should contain the selected value and the facet name in the breadcrumb button', async ({ + breadbox, + }) => { + const breadcrumbButton = breadbox.getBreadcrumbButtons().first(); + + await expect(breadcrumbButton).toHaveText(`Category:${breadcrumbText}`); + }); + }); + }); + test.describe('when a numerical facet value is selected', () => { + let firstValueText: string | RegExp; + + test.beforeEach(async ({breadbox}) => { + await breadbox.getFacetValue('numerical').first().click(); + firstValueText = (await breadbox + .getFacetValue('numerical') + .locator('span') + .first() + .textContent()) as string; + await breadbox + .getBreadcrumbButtons(firstValueText) + .waitFor({state: 'visible'}); + }); + + test('should disappear when clicking on the breadcrumb button', async ({ + breadbox, + }) => { + const breadcrumbButton = breadbox.getBreadcrumbButtons(firstValueText); + await breadcrumbButton.click(); + + await expect(breadcrumbButton).not.toBeVisible(); + }); + + test('should contain the selected value and the facet name in the breadcrumb button', async ({ + breadbox, + }) => { + const breadcrumbButton = breadbox.getBreadcrumbButtons(firstValueText); + + await expect(breadcrumbButton).toHaveText('Price:' + firstValueText); + }); + }); + + test.describe('when a date range facet value is selected', () => { + let firstValueText: string | RegExp; + + test.beforeEach(async ({breadbox}) => { + await breadbox.getFacetValue('dateRange').first().click(); + firstValueText = (await breadbox + .getFacetValue('dateRange') + .first() + .locator('span') + .nth(1) + .textContent()) as string; + await breadbox + .getBreadcrumbButtons(firstValueText) + .waitFor({state: 'visible'}); + }); + + test('should disappear when clicking on the breadcrumb button', async ({ + breadbox, + }) => { + const breadcrumbButton = breadbox.getBreadcrumbButtons(firstValueText); + await breadcrumbButton.click(); + + await expect(breadcrumbButton).not.toBeVisible(); + }); + + test('should contain the selected value and the facet name in the breadcrumb button', async ({ + breadbox, + }) => { + const breadcrumbButton = breadbox.getBreadcrumbButtons(firstValueText); + + await expect(breadcrumbButton).toHaveText('Date:' + firstValueText); + }); + }); + + test.describe('when selecting multiple facet values', () => { + test.beforeEach(async ({breadbox}) => { + for (let i = 0; i < 6; i++) { + await breadbox.getFacetValue('regular').nth(i).click(); + await breadbox + .getBreadcrumbButtons() + .nth(i) + .waitFor({state: 'visible'}); + } + }); + + test('should display the "Clear all" button', async ({breadbox}) => { + await expect(breadbox.getClearAllButton()).toBeVisible(); + }); + + test.describe('when clicking on the "Clear all" button', () => { + test.beforeEach(async ({breadbox}) => { + await breadbox.getClearAllButton().click(); + }); + + test('should hide the "Clear all" button', async ({breadbox}) => { + await expect(breadbox.getClearAllButton()).not.toBeVisible(); + }); + + test('should hide all breadcrumb buttons', async ({breadbox}) => { + await breadbox.getClearAllButton().waitFor({state: 'hidden'}); + + expect(await breadbox.getBreadcrumbButtons().count()).toBe(0); + }); + }); + + test('should group extra facet values in the "Show More" button based on the viewport size', async ({ + breadbox, + page, + }) => { + await expect(breadbox.getShowMorebutton()).not.toBeVisible(); + expect(await breadbox.getBreadcrumbButtons().count()).toBe(6); + + await page.setViewportSize({width: 240, height: 480}); + await breadbox.getBreadcrumbButtons().first().waitFor({state: 'hidden'}); + await expect(breadbox.getShowMorebutton()).toContainText('+ 6'); + + await page.setViewportSize({width: 1920, height: 480}); + await breadbox.getBreadcrumbButtons().first().waitFor({state: 'visible'}); + await expect(breadbox.getShowMorebutton()).not.toBeVisible(); + expect(await breadbox.getBreadcrumbButtons().count()).toBe(6); + }); + + test.describe('when clicking on a breadcrumb button', () => { + let firstButton: Locator; + let firstButtonText: string | RegExp; + + test.beforeEach(async ({breadbox, page}) => { + firstButton = breadbox.getBreadcrumbButtons().first(); + firstButtonText = (await firstButton.textContent()) as string; + await firstButton.click(); + await page.setViewportSize({width: 240, height: 480}); + }); + + test('should hide the breadcrumb button', async ({breadbox}) => { + await expect( + breadbox.getBreadcrumbButtons(firstButtonText) + ).not.toBeVisible(); + }); + + test('should uncheck the facet value', async ({breadbox}) => { + const facetValueButton = breadbox.getFacetValue( + 'regular', + firstButtonText + ); + if (await facetValueButton.isVisible()) { + await expect(facetValueButton).not.toBeChecked(); + } + }); + + test('should update the "Show More" button count', async ({breadbox}) => { + await expect(breadbox.getShowMorebutton()).toContainText('+ 5'); + }); + }); + + test.describe('when clicking on the "Show More" button', () => { + test.beforeEach(async ({breadbox, page}) => { + await page.setViewportSize({width: 640, height: 480}); + await breadbox.getShowMorebutton().click(); + }); + + test('should display all facet values', async ({breadbox}) => { + await expect(breadbox.getShowMorebutton()).not.toBeVisible(); + expect(await breadbox.getBreadcrumbButtons().count()).toBe(6); + }); + + test('should display the "Show Less" button', async ({breadbox}) => { + await expect(breadbox.getShowMorebutton()).not.toBeVisible(); + await expect(breadbox.getShowLessbutton()).toBeVisible(); + }); + + test('should show the "Show More" button when clicking on the "Show Less" button', async ({ + breadbox, + }) => { + await breadbox.getShowLessbutton().click(); + await expect(breadbox.getShowMorebutton()).toBeVisible(); + await expect(breadbox.getShowLessbutton()).not.toBeVisible(); + }); + }); + }); +}); diff --git a/packages/atomic/src/components/commerce/atomic-commerce-breadbox/e2e/fixture.ts b/packages/atomic/src/components/commerce/atomic-commerce-breadbox/e2e/fixture.ts new file mode 100644 index 00000000000..7133ef1e219 --- /dev/null +++ b/packages/atomic/src/components/commerce/atomic-commerce-breadbox/e2e/fixture.ts @@ -0,0 +1,18 @@ +import {test as base} from '@playwright/test'; +import { + AxeFixture, + makeAxeBuilder, +} from '../../../../../playwright-utils/base-fixture'; +import {AtomicCommerceBreadboxPageObject as Breadbox} from './page-object'; + +type MyFixtures = { + breadbox: Breadbox; +}; + +export const test = base.extend({ + makeAxeBuilder, + breadbox: async ({page}, use) => { + await use(new Breadbox(page)); + }, +}); +export {expect} from '@playwright/test'; diff --git a/packages/atomic/src/components/commerce/atomic-commerce-breadbox/e2e/page-object.ts b/packages/atomic/src/components/commerce/atomic-commerce-breadbox/e2e/page-object.ts new file mode 100644 index 00000000000..fd26f53c28e --- /dev/null +++ b/packages/atomic/src/components/commerce/atomic-commerce-breadbox/e2e/page-object.ts @@ -0,0 +1,50 @@ +import type {Page} from '@playwright/test'; +import {BasePageObject} from '../../../../../playwright-utils/base-page-object'; + +export class AtomicCommerceBreadboxPageObject extends BasePageObject<'atomic-commerce-breadbox'> { + constructor(page: Page) { + super(page, 'atomic-commerce-breadbox'); + } + + getFacetValue( + facetType: + | 'regular' + | 'category' + | 'nestedCategory' + | 'numerical' + | 'dateRange', + value?: string | RegExp + ) { + const facetTypeLocators = { + regular: 'atomic-commerce-facet', + category: 'atomic-commerce-category-facet', + nestedCategory: 'atomic-commerce-category-facet [part="values"]', + numerical: 'atomic-commerce-numeric-facet', + dateRange: 'atomic-commerce-timeframe-facet', + }; + + const baseLocator = this.page + .locator(facetTypeLocators[facetType]) + .getByRole('listitem'); + return value ? baseLocator.filter({hasText: value}) : baseLocator; + } + + getBreadcrumbButtons(value?: string | RegExp) { + const baseLocator = this.page.getByLabel('Remove Inclusion filter on', { + exact: false, + }); + return value ? baseLocator.filter({hasText: value}) : baseLocator; + } + + getShowMorebutton() { + return this.page.getByLabel(/Show \d+ more filters/); + } + + getClearAllButton() { + return this.page.getByLabel('Clear all filters'); + } + + getShowLessbutton() { + return this.page.getByRole('button').filter({hasText: /Show less/}); + } +} diff --git a/packages/atomic/storybookUtils/commerce-interface-wrapper.tsx b/packages/atomic/storybookUtils/commerce-interface-wrapper.tsx index 038f7c81424..87ecd072466 100644 --- a/packages/atomic/storybookUtils/commerce-interface-wrapper.tsx +++ b/packages/atomic/storybookUtils/commerce-interface-wrapper.tsx @@ -10,15 +10,17 @@ import type * as _ from '../src/components.d.ts'; export const wrapInCommerceInterface = ({ engineConfig, skipFirstSearch, + type = 'search', }: { engineConfig?: Partial; skipFirstSearch?: boolean; + type?: 'search' | 'product-listing'; }): { decorator: Decorator; play: (context: StoryContext) => Promise; } => ({ decorator: (story) => html` - + ${story()} `, From d978b7b8a7194263d094679f6c6788cdfb88f4b7 Mon Sep 17 00:00:00 2001 From: "developer-experience-bot[bot]" <91079284+developer-experience-bot[bot]@users.noreply.github.com> Date: Wed, 31 Jul 2024 14:00:51 +0000 Subject: [PATCH 06/23] [Version Bump][skip ci]: ui-kit publish @coveo/bueno@0.46.1 @coveo/headless@2.75.0 @coveo/atomic@2.75.0 @coveo/headless-react@1.0.21 @coveo/atomic-react@2.13.0 @coveo/auth@1.11.22 @coveo/atomic-hosted-page@0.6.2 @coveo/quantic@2.54.1 **/CHANGELOG.md **/package.json CHANGELOG.md package.json package-lock.json --- package-lock.json | 86 +++++++++---------- packages/atomic-angular/package.json | 6 +- .../projects/atomic-angular/package.json | 4 +- packages/atomic-hosted-page/CHANGELOG.md | 2 + packages/atomic-hosted-page/package.json | 6 +- packages/atomic-react/CHANGELOG.md | 6 ++ packages/atomic-react/package.json | 8 +- packages/atomic/CHANGELOG.md | 7 ++ packages/atomic/package.json | 8 +- packages/auth/CHANGELOG.md | 2 + packages/auth/package.json | 2 +- packages/bueno/CHANGELOG.md | 2 + packages/bueno/package.json | 2 +- packages/headless-react/CHANGELOG.md | 2 + packages/headless-react/package.json | 4 +- packages/headless/CHANGELOG.md | 12 +++ packages/headless/package.json | 4 +- packages/quantic/CHANGELOG.md | 6 ++ packages/quantic/package.json | 6 +- packages/samples/atomic-next/package.json | 6 +- packages/samples/atomic-react/package.json | 6 +- .../headless-commerce-react/package.json | 2 +- packages/samples/headless-react/package.json | 4 +- packages/samples/headless-ssr/package.json | 4 +- packages/samples/iife/package.json | 8 +- packages/samples/stencil/package.json | 4 +- packages/samples/vuejs/package.json | 2 +- 27 files changed, 125 insertions(+), 86 deletions(-) diff --git a/package-lock.json b/package-lock.json index b045afc8d47..87f5282430d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55327,10 +55327,10 @@ }, "packages/atomic": { "name": "@coveo/atomic", - "version": "2.74.0", + "version": "2.75.0", "license": "Apache-2.0", "dependencies": { - "@coveo/bueno": "0.46.0", + "@coveo/bueno": "0.46.1", "@popperjs/core": "^2.11.6", "@salesforce-ux/design-system": "^2.16.1", "@stencil/core": "4.19.2", @@ -55349,7 +55349,7 @@ "@axe-core/playwright": "4.9.1", "@babel/core": "7.24.9", "@coveo/atomic": "file:.", - "@coveo/headless": "2.74.0", + "@coveo/headless": "2.75.0", "@coveo/release": "1.0.0", "@custom-elements-manifest/analyzer": "0.10.3", "@fullhuman/postcss-purgecss": "6.0.0", @@ -55424,7 +55424,7 @@ "node": ">=12.9.0" }, "peerDependencies": { - "@coveo/headless": "2.74.0" + "@coveo/headless": "2.75.0" } }, "packages/atomic-angular": { @@ -55439,14 +55439,14 @@ "@angular/platform-browser": "17.3.12", "@angular/platform-browser-dynamic": "17.3.12", "@angular/router": "17.3.12", - "@coveo/atomic": "2.74.0", + "@coveo/atomic": "2.75.0", "rxjs": "7.8.1" }, "devDependencies": { "@angular-devkit/build-angular": "17.3.8", "@angular/cli": "17.3.8", "@angular/compiler-cli": "17.3.12", - "@coveo/headless": "2.74.0", + "@coveo/headless": "2.75.0", "@types/jasmine": "5.1.4", "@types/node": "20.14.12", "jasmine-core": "5.2.0", @@ -55460,7 +55460,7 @@ "typescript": "5.4.5" }, "peerDependencies": { - "@coveo/headless": "2.74.0" + "@coveo/headless": "2.75.0" } }, "packages/atomic-angular/node_modules/jasmine-core": { @@ -55492,22 +55492,22 @@ "version": "2.25.5", "license": "Apache-2.0", "dependencies": { - "@coveo/atomic": "2.74.0", + "@coveo/atomic": "2.75.0", "tslib": "2.6.3" }, "peerDependencies": { "@angular/common": "14 - 17", "@angular/core": "14 - 17", - "@coveo/headless": "2.74.0" + "@coveo/headless": "2.75.0" } }, "packages/atomic-hosted-page": { "name": "@coveo/atomic-hosted-page", - "version": "0.6.1", + "version": "0.6.2", "license": "Apache-2.0", "dependencies": { - "@coveo/bueno": "0.46.0", - "@coveo/headless": "2.74.0", + "@coveo/bueno": "0.46.1", + "@coveo/headless": "2.75.0", "@stencil/core": "4.19.2" }, "devDependencies": { @@ -55586,12 +55586,12 @@ }, "packages/atomic-react": { "name": "@coveo/atomic-react", - "version": "2.12.2", + "version": "2.13.0", "dependencies": { - "@coveo/atomic": "2.74.0" + "@coveo/atomic": "2.75.0" }, "devDependencies": { - "@coveo/headless": "2.74.0", + "@coveo/headless": "2.75.0", "@coveo/release": "1.0.0", "@rollup/plugin-commonjs": "^25.0.0", "@rollup/plugin-node-resolve": "^15.0.0", @@ -55608,7 +55608,7 @@ "rollup-plugin-polyfill-node": "^0.13.0" }, "peerDependencies": { - "@coveo/headless": "2.74.0", + "@coveo/headless": "2.75.0", "react": ">=18.0.0", "react-dom": ">=18.0.0" } @@ -58059,7 +58059,7 @@ }, "packages/auth": { "name": "@coveo/auth", - "version": "1.11.21", + "version": "1.11.22", "license": "Apache-2.0", "devDependencies": { "@coveo/release": "1.0.0", @@ -58659,7 +58659,7 @@ }, "packages/bueno": { "name": "@coveo/bueno", - "version": "0.46.0", + "version": "0.46.1", "license": "Apache-2.0", "devDependencies": { "@coveo/release": "1.0.0", @@ -58717,10 +58717,10 @@ }, "packages/headless": { "name": "@coveo/headless", - "version": "2.74.0", + "version": "2.75.0", "license": "Apache-2.0", "dependencies": { - "@coveo/bueno": "0.46.0", + "@coveo/bueno": "0.46.1", "@coveo/relay": "0.7.10", "@coveo/relay-event-types": "9.4.0", "@microsoft/fetch-event-source": "2.0.1", @@ -58760,10 +58760,10 @@ }, "packages/headless-react": { "name": "@coveo/headless-react", - "version": "1.0.20", + "version": "1.0.21", "license": "Apache-2.0", "dependencies": { - "@coveo/headless": "2.74.0" + "@coveo/headless": "2.75.0" }, "devDependencies": { "@coveo/release": "1.0.0", @@ -59318,12 +59318,12 @@ }, "packages/quantic": { "name": "@coveo/quantic", - "version": "2.54.0", + "version": "2.54.1", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@coveo/bueno": "0.46.0", - "@coveo/headless": "2.74.0", + "@coveo/bueno": "0.46.1", + "@coveo/headless": "2.75.0", "dompurify": "3.1.6", "marked": "12.0.2" }, @@ -60635,9 +60635,9 @@ "name": "@coveo/atomic-next-samples", "version": "0.0.0", "dependencies": { - "@coveo/atomic": "2.74.0", - "@coveo/atomic-react": "2.12.2", - "@coveo/headless": "2.74.0", + "@coveo/atomic": "2.75.0", + "@coveo/atomic-react": "2.13.0", + "@coveo/headless": "2.75.0", "next": "14.2.5", "react": "18.3.1", "react-dom": "18.3.1" @@ -60916,9 +60916,9 @@ "name": "@coveo/atomic-react-samples", "version": "0.0.0", "dependencies": { - "@coveo/atomic": "2.74.0", - "@coveo/atomic-react": "2.12.2", - "@coveo/headless": "2.74.0", + "@coveo/atomic": "2.75.0", + "@coveo/atomic-react": "2.13.0", + "@coveo/headless": "2.75.0", "react": "18.3.1", "react-dom": "18.3.1" }, @@ -61160,7 +61160,7 @@ "name": "@coveo/headless-commerce-react-samples", "version": "0.1.0", "dependencies": { - "@coveo/headless": "^2.74.0", + "@coveo/headless": "2.75.0", "@testing-library/jest-dom": "6.4.8", "@testing-library/react": "16.0.0", "@testing-library/user-event": "14.5.2", @@ -63627,8 +63627,8 @@ "name": "@coveo/headless-react-samples", "version": "0.0.0", "dependencies": { - "@coveo/auth": "1.11.21", - "@coveo/headless": "2.74.0", + "@coveo/auth": "1.11.22", + "@coveo/headless": "2.75.0", "@testing-library/jest-dom": "6.4.8", "@testing-library/react": "14.3.1", "@testing-library/user-event": "14.5.2", @@ -67061,8 +67061,8 @@ "name": "@coveo/headless-ssr-samples-common", "version": "0.0.0", "dependencies": { - "@coveo/headless": "2.74.0", - "@coveo/headless-react": "1.0.20", + "@coveo/headless": "2.75.0", + "@coveo/headless-react": "1.0.21", "next": "14.2.5", "react": "18.3.1", "react-dom": "18.3.1" @@ -67575,10 +67575,10 @@ "version": "0.1.0", "dependencies": { "@babel/standalone": "7.25.0", - "@coveo/atomic": "2.74.0", - "@coveo/atomic-hosted-page": "0.6.1", - "@coveo/atomic-react": "2.12.2", - "@coveo/headless": "2.74.0", + "@coveo/atomic": "2.75.0", + "@coveo/atomic-hosted-page": "0.6.2", + "@coveo/atomic-react": "2.13.0", + "@coveo/headless": "2.75.0", "react": "18.3.1", "react-dom": "18.3.1" }, @@ -67647,8 +67647,8 @@ "name": "@coveo/atomic-stencil-samples", "version": "0.0.0", "dependencies": { - "@coveo/atomic": "2.74.0", - "@coveo/headless": "2.74.0", + "@coveo/atomic": "2.75.0", + "@coveo/headless": "2.75.0", "@stencil/core": "4.19.2", "stencil-router-v2": "0.6.0" }, @@ -67708,7 +67708,7 @@ "name": "@coveo/atomic-vuejs-samples", "version": "0.0.0", "dependencies": { - "@coveo/atomic": "2.74.0", + "@coveo/atomic": "2.75.0", "vue": "^3.4.15" }, "devDependencies": { diff --git a/packages/atomic-angular/package.json b/packages/atomic-angular/package.json index 6df6357c879..164ecc1a393 100644 --- a/packages/atomic-angular/package.json +++ b/packages/atomic-angular/package.json @@ -20,17 +20,17 @@ "@angular/platform-browser": "17.3.12", "@angular/platform-browser-dynamic": "17.3.12", "@angular/router": "17.3.12", - "@coveo/atomic": "2.74.0", + "@coveo/atomic": "2.75.0", "rxjs": "7.8.1" }, "peerDependencies": { - "@coveo/headless": "2.74.0" + "@coveo/headless": "2.75.0" }, "devDependencies": { "@angular-devkit/build-angular": "17.3.8", "@angular/cli": "17.3.8", "@angular/compiler-cli": "17.3.12", - "@coveo/headless": "2.74.0", + "@coveo/headless": "2.75.0", "@types/jasmine": "5.1.4", "@types/node": "20.14.12", "jasmine-core": "5.2.0", diff --git a/packages/atomic-angular/projects/atomic-angular/package.json b/packages/atomic-angular/projects/atomic-angular/package.json index 73d93182a07..4dad21f5718 100644 --- a/packages/atomic-angular/projects/atomic-angular/package.json +++ b/packages/atomic-angular/projects/atomic-angular/package.json @@ -8,10 +8,10 @@ "peerDependencies": { "@angular/common": "14 - 17", "@angular/core": "14 - 17", - "@coveo/headless": "2.74.0" + "@coveo/headless": "2.75.0" }, "dependencies": { - "@coveo/atomic": "2.74.0", + "@coveo/atomic": "2.75.0", "tslib": "2.6.3" } } diff --git a/packages/atomic-hosted-page/CHANGELOG.md b/packages/atomic-hosted-page/CHANGELOG.md index cd20c7ea8c3..4a6259ea788 100644 --- a/packages/atomic-hosted-page/CHANGELOG.md +++ b/packages/atomic-hosted-page/CHANGELOG.md @@ -1,3 +1,5 @@ +## 0.6.2 (2024-07-31) + # 0.6.0 (2024-07-17) ### Bug Fixes diff --git a/packages/atomic-hosted-page/package.json b/packages/atomic-hosted-page/package.json index d8789aeee28..1b453765e67 100644 --- a/packages/atomic-hosted-page/package.json +++ b/packages/atomic-hosted-page/package.json @@ -1,7 +1,7 @@ { "name": "@coveo/atomic-hosted-page", "description": "Web Component used to inject a Coveo Hosted Search Page in the DOM.", - "version": "0.6.1", + "version": "0.6.2", "repository": { "type": "git", "url": "git+https://github.com/coveo/ui-kit.git", @@ -30,8 +30,8 @@ "promote:npm:latest": "node ../../scripts/deploy/update-npm-tag.mjs latest" }, "dependencies": { - "@coveo/bueno": "0.46.0", - "@coveo/headless": "2.74.0", + "@coveo/bueno": "0.46.1", + "@coveo/headless": "2.75.0", "@stencil/core": "4.19.2" }, "devDependencies": { diff --git a/packages/atomic-react/CHANGELOG.md b/packages/atomic-react/CHANGELOG.md index 81388d2060e..2b3d50c9625 100644 --- a/packages/atomic-react/CHANGELOG.md +++ b/packages/atomic-react/CHANGELOG.md @@ -1,3 +1,9 @@ +# 2.13.0 (2024-07-31) + +### Features + +- **atomic-commerce:** products-per-page ([#4107](https://github.com/coveo/ui-kit/issues/4107)) ([81e31cf](https://github.com/coveo/ui-kit/commits/81e31cff63c19f19b12babd4b10aa5b2e60c19e6)) + # 2.12.0 (2024-07-09) ### Features diff --git a/packages/atomic-react/package.json b/packages/atomic-react/package.json index 8c1531f2105..53817ab8f8b 100644 --- a/packages/atomic-react/package.json +++ b/packages/atomic-react/package.json @@ -1,7 +1,7 @@ { "name": "@coveo/atomic-react", "sideEffects": false, - "version": "2.12.2", + "version": "2.13.0", "description": "React specific wrapper for the Atomic component library", "repository": { "type": "git", @@ -29,11 +29,11 @@ "commerce/" ], "dependencies": { - "@coveo/atomic": "2.74.0" + "@coveo/atomic": "2.75.0" }, "devDependencies": { "@coveo/release": "1.0.0", - "@coveo/headless": "2.74.0", + "@coveo/headless": "2.75.0", "@rollup/plugin-commonjs": "^25.0.0", "@rollup/plugin-node-resolve": "^15.0.0", "@rollup/plugin-replace": "^5.0.0", @@ -49,7 +49,7 @@ "@rollup/plugin-terser": "0.4.4" }, "peerDependencies": { - "@coveo/headless": "2.74.0", + "@coveo/headless": "2.75.0", "react": ">=18.0.0", "react-dom": ">=18.0.0" } diff --git a/packages/atomic/CHANGELOG.md b/packages/atomic/CHANGELOG.md index 087edf5358e..0eb4a82cd6a 100644 --- a/packages/atomic/CHANGELOG.md +++ b/packages/atomic/CHANGELOG.md @@ -1,3 +1,10 @@ +# 2.75.0 (2024-07-31) + +### Features + +- **atomic-commerce:** products-per-page ([#4107](https://github.com/coveo/ui-kit/issues/4107)) ([81e31cf](https://github.com/coveo/ui-kit/commits/81e31cff63c19f19b12babd4b10aa5b2e60c19e6)) +- **atomic:** add atomic-tab-manager component ([#4196](https://github.com/coveo/ui-kit/issues/4196)) ([523ab9b](https://github.com/coveo/ui-kit/commits/523ab9be9ba9bf8d14ef304d98726680bea63b71)) + # 2.74.0 (2024-07-24) ### Bug Fixes diff --git a/packages/atomic/package.json b/packages/atomic/package.json index 05402a2a972..ec92c569488 100644 --- a/packages/atomic/package.json +++ b/packages/atomic/package.json @@ -1,6 +1,6 @@ { "name": "@coveo/atomic", - "version": "2.74.0", + "version": "2.75.0", "description": "A web-component library for building modern UIs interfacing with the Coveo platform", "homepage": "https://docs.coveo.com/en/atomic/latest/", "repository": { @@ -47,7 +47,7 @@ "validate:definitions": "tsc --noEmit --esModuleInterop --skipLibCheck ./dist/types/components.d.ts" }, "dependencies": { - "@coveo/bueno": "0.46.0", + "@coveo/bueno": "0.46.1", "@popperjs/core": "^2.11.6", "@salesforce-ux/design-system": "^2.16.1", "@stencil/core": "4.19.2", @@ -66,7 +66,7 @@ "@axe-core/playwright": "4.9.1", "@babel/core": "7.24.9", "@coveo/atomic": "file:.", - "@coveo/headless": "2.74.0", + "@coveo/headless": "2.75.0", "@coveo/release": "1.0.0", "@custom-elements-manifest/analyzer": "0.10.3", "@fullhuman/postcss-purgecss": "6.0.0", @@ -138,7 +138,7 @@ "wait-on": "7.2.0" }, "peerDependencies": { - "@coveo/headless": "2.74.0" + "@coveo/headless": "2.75.0" }, "license": "Apache-2.0", "engines": { diff --git a/packages/auth/CHANGELOG.md b/packages/auth/CHANGELOG.md index 3406e30743f..4fa8d2b7a12 100644 --- a/packages/auth/CHANGELOG.md +++ b/packages/auth/CHANGELOG.md @@ -1,3 +1,5 @@ +## 1.11.22 (2024-07-31) + ## 1.11.21 (2024-06-06) ## 1.11.20 (2024-05-15) diff --git a/packages/auth/package.json b/packages/auth/package.json index 1fcc2c72602..27bab812f9e 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -1,6 +1,6 @@ { "name": "@coveo/auth", - "version": "1.11.21", + "version": "1.11.22", "description": "Functions to help authenticate with the Coveo platform.", "main": "./dist/auth.js", "module": "./dist/auth.esm.js", diff --git a/packages/bueno/CHANGELOG.md b/packages/bueno/CHANGELOG.md index 685efae4f65..6504668f0b3 100644 --- a/packages/bueno/CHANGELOG.md +++ b/packages/bueno/CHANGELOG.md @@ -1,3 +1,5 @@ +## 0.46.1 (2024-07-31) + # 0.46.0 (2024-07-24) ### Features diff --git a/packages/bueno/package.json b/packages/bueno/package.json index dd525c1b704..2d01301b3fd 100644 --- a/packages/bueno/package.json +++ b/packages/bueno/package.json @@ -13,7 +13,7 @@ }, "types": "./dist/definitions/index.d.ts", "license": "Apache-2.0", - "version": "0.46.0", + "version": "0.46.1", "files": [ "dist/" ], diff --git a/packages/headless-react/CHANGELOG.md b/packages/headless-react/CHANGELOG.md index 170116e045b..b124666b55f 100644 --- a/packages/headless-react/CHANGELOG.md +++ b/packages/headless-react/CHANGELOG.md @@ -1,3 +1,5 @@ +## 1.0.21 (2024-07-31) + ## 1.0.9 (2024-06-06) ## 1.0.6 (2024-05-15) diff --git a/packages/headless-react/package.json b/packages/headless-react/package.json index 139162bb632..654f8529a7f 100644 --- a/packages/headless-react/package.json +++ b/packages/headless-react/package.json @@ -1,6 +1,6 @@ { "name": "@coveo/headless-react", - "version": "1.0.20", + "version": "1.0.21", "description": "React utilities for SSR (Server Side Rendering) with headless", "homepage": "https://docs.coveo.com/en/headless/latest/", "repository": { @@ -33,7 +33,7 @@ "promote:npm:latest": "node ../../scripts/deploy/update-npm-tag.mjs latest" }, "dependencies": { - "@coveo/headless": "2.74.0" + "@coveo/headless": "2.75.0" }, "devDependencies": { "@coveo/release": "1.0.0", diff --git a/packages/headless/CHANGELOG.md b/packages/headless/CHANGELOG.md index fcf41e8904b..be46ba88ed8 100644 --- a/packages/headless/CHANGELOG.md +++ b/packages/headless/CHANGELOG.md @@ -1,3 +1,15 @@ +# 2.75.0 (2024-07-31) + +### Bug Fixes + +- **deps:** update dependency @reduxjs/toolkit to v2.2.7 j:kit-282 ([#4232](https://github.com/coveo/ui-kit/issues/4232)) ([0d28438](https://github.com/coveo/ui-kit/commits/0d2843805abd8e0305b3349732554e63a3fdde64)) +- **ep:** do not send actionCause w/ breadcrumbResetAll ([#4207](https://github.com/coveo/ui-kit/issues/4207)) ([bcecc55](https://github.com/coveo/ui-kit/commits/bcecc552b433b5da3b9940bae96c725ea731df90)) +- **headless/commerce:** send clientId only when analytics are enabled ([#4217](https://github.com/coveo/ui-kit/issues/4217)) ([323cede](https://github.com/coveo/ui-kit/commits/323cedefc60292ec9193b01e500372ddde1ebcc6)) + +### Features + +- **headless SSR:** define the SSR Commerce Engine ([#4198](https://github.com/coveo/ui-kit/issues/4198)) ([c474a8d](https://github.com/coveo/ui-kit/commits/c474a8d9cb77aeb5b88a992847e5a233adb55123)) + # 2.74.0 (2024-07-24) ### Bug Fixes diff --git a/packages/headless/package.json b/packages/headless/package.json index 206c9b3a672..f8e6a7f2567 100644 --- a/packages/headless/package.json +++ b/packages/headless/package.json @@ -14,7 +14,7 @@ }, "types": "./dist/definitions/index.d.ts", "license": "Apache-2.0", - "version": "2.74.0", + "version": "2.75.0", "files": [ "dist/", "recommendation/", @@ -49,7 +49,7 @@ "pino-pretty": "^6.0.0 || ^10.0.0 || ^11.0.0" }, "dependencies": { - "@coveo/bueno": "0.46.0", + "@coveo/bueno": "0.46.1", "@coveo/relay": "0.7.10", "@coveo/relay-event-types": "9.4.0", "@microsoft/fetch-event-source": "2.0.1", diff --git a/packages/quantic/CHANGELOG.md b/packages/quantic/CHANGELOG.md index c266b41db56..8d6aeb26fe7 100644 --- a/packages/quantic/CHANGELOG.md +++ b/packages/quantic/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.54.1 (2024-07-31) + +### Bug Fixes + +- **ep:** do not send actionCause w/ breadcrumbResetAll ([#4207](https://github.com/coveo/ui-kit/issues/4207)) ([bcecc55](https://github.com/coveo/ui-kit/commits/bcecc552b433b5da3b9940bae96c725ea731df90)) + # 2.54.0 (2024-07-24) ### Bug Fixes diff --git a/packages/quantic/package.json b/packages/quantic/package.json index 94e7a04aaae..a932417a8b2 100644 --- a/packages/quantic/package.json +++ b/packages/quantic/package.json @@ -1,6 +1,6 @@ { "name": "@coveo/quantic", - "version": "2.54.0", + "version": "2.54.1", "description": "A Salesforce Lightning Web Component (LWC) library for building modern UIs interfacing with the Coveo platform", "author": "coveo.com", "homepage": "https://coveo.com", @@ -45,8 +45,8 @@ "postinstall": "node scripts/npm/setup-quantic.js" }, "dependencies": { - "@coveo/bueno": "0.46.0", - "@coveo/headless": "2.74.0", + "@coveo/bueno": "0.46.1", + "@coveo/headless": "2.75.0", "dompurify": "3.1.6", "marked": "12.0.2" }, diff --git a/packages/samples/atomic-next/package.json b/packages/samples/atomic-next/package.json index 71ff14a4c01..2bb191165e4 100644 --- a/packages/samples/atomic-next/package.json +++ b/packages/samples/atomic-next/package.json @@ -3,9 +3,9 @@ "version": "0.0.0", "private": true, "dependencies": { - "@coveo/atomic": "2.74.0", - "@coveo/atomic-react": "2.12.2", - "@coveo/headless": "2.74.0", + "@coveo/atomic": "2.75.0", + "@coveo/atomic-react": "2.13.0", + "@coveo/headless": "2.75.0", "next": "14.2.5", "react": "18.3.1", "react-dom": "18.3.1" diff --git a/packages/samples/atomic-react/package.json b/packages/samples/atomic-react/package.json index 206fbeaa12f..1f8629bc3f7 100644 --- a/packages/samples/atomic-react/package.json +++ b/packages/samples/atomic-react/package.json @@ -4,9 +4,9 @@ "description": "Samples with atomic-react", "private": true, "dependencies": { - "@coveo/atomic": "2.74.0", - "@coveo/atomic-react": "2.12.2", - "@coveo/headless": "2.74.0", + "@coveo/atomic": "2.75.0", + "@coveo/atomic-react": "2.13.0", + "@coveo/headless": "2.75.0", "react": "18.3.1", "react-dom": "18.3.1" }, diff --git a/packages/samples/headless-commerce-react/package.json b/packages/samples/headless-commerce-react/package.json index 7a592c3a506..f5086b93bff 100644 --- a/packages/samples/headless-commerce-react/package.json +++ b/packages/samples/headless-commerce-react/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { - "@coveo/headless": "^2.74.0", + "@coveo/headless": "2.75.0", "@testing-library/jest-dom": "6.4.8", "@testing-library/react": "16.0.0", "@testing-library/user-event": "14.5.2", diff --git a/packages/samples/headless-react/package.json b/packages/samples/headless-react/package.json index 55fc24eefe9..c69e14f8c52 100644 --- a/packages/samples/headless-react/package.json +++ b/packages/samples/headless-react/package.json @@ -4,8 +4,8 @@ "version": "0.0.0", "private": true, "dependencies": { - "@coveo/auth": "1.11.21", - "@coveo/headless": "2.74.0", + "@coveo/auth": "1.11.22", + "@coveo/headless": "2.75.0", "@testing-library/jest-dom": "6.4.8", "@testing-library/react": "14.3.1", "@testing-library/user-event": "14.5.2", diff --git a/packages/samples/headless-ssr/package.json b/packages/samples/headless-ssr/package.json index 765b12ead40..902cc96616b 100644 --- a/packages/samples/headless-ssr/package.json +++ b/packages/samples/headless-ssr/package.json @@ -8,8 +8,8 @@ "e2e:watch": "cypress open --browser chrome --e2e" }, "dependencies": { - "@coveo/headless-react": "1.0.20", - "@coveo/headless": "2.74.0", + "@coveo/headless-react": "1.0.21", + "@coveo/headless": "2.75.0", "next": "14.2.5", "react": "18.3.1", "react-dom": "18.3.1" diff --git a/packages/samples/iife/package.json b/packages/samples/iife/package.json index 631fc0d209b..688b15f906e 100644 --- a/packages/samples/iife/package.json +++ b/packages/samples/iife/package.json @@ -12,10 +12,10 @@ }, "dependencies": { "@babel/standalone": "7.25.0", - "@coveo/atomic": "2.74.0", - "@coveo/atomic-hosted-page": "0.6.1", - "@coveo/atomic-react": "2.12.2", - "@coveo/headless": "2.74.0", + "@coveo/atomic": "2.75.0", + "@coveo/atomic-hosted-page": "0.6.2", + "@coveo/atomic-react": "2.13.0", + "@coveo/headless": "2.75.0", "react": "18.3.1", "react-dom": "18.3.1" }, diff --git a/packages/samples/stencil/package.json b/packages/samples/stencil/package.json index c5b79f67a9c..e4b9ac6f03b 100644 --- a/packages/samples/stencil/package.json +++ b/packages/samples/stencil/package.json @@ -8,8 +8,8 @@ "e2e:watch": "cypress open --browser chrome --e2e" }, "dependencies": { - "@coveo/atomic": "2.74.0", - "@coveo/headless": "2.74.0", + "@coveo/atomic": "2.75.0", + "@coveo/headless": "2.75.0", "@stencil/core": "4.19.2", "stencil-router-v2": "0.6.0" }, diff --git a/packages/samples/vuejs/package.json b/packages/samples/vuejs/package.json index 46d9c949fa3..d8eccef70cb 100644 --- a/packages/samples/vuejs/package.json +++ b/packages/samples/vuejs/package.json @@ -13,7 +13,7 @@ }, "dependencies": { "vue": "^3.4.15", - "@coveo/atomic": "2.74.0" + "@coveo/atomic": "2.75.0" }, "devDependencies": { "@vitejs/plugin-vue": "^5.0.3", From e6806409501be9ea272f5781c834f69bcd4e5431 Mon Sep 17 00:00:00 2001 From: Danny Gauthier Date: Wed, 31 Jul 2024 15:13:15 -0400 Subject: [PATCH 07/23] feat(answerApi): evaluations (#4239) # Answer Api [simplescreenrecorder-2024-07-30_16.55.09.webm](https://github.com/user-attachments/assets/79153f5a-19e6-42ec-8a3b-1c3921783e81) ## Summary This PR make it so we can send CRGA evaluations from the feedback modal to the AnswerApi - The endpoint that we can POST evaluations to has been added - The Steam answer client will get the header of the stream response and set the Answer ID in its state - The Controller now overrides the `SendFeedback` method in order to POST the feedback to the answer api ### Post Evaluation Endpoint A RTK query Endpoint have been added to the Answer slice A little modification to the dynamicBaseQuery was beneficial to make the endpoint definition simpler. ### Answer API Stream Headers recuperation We use the `x-answer-id` from the stream call to identify the feedback when we send it to the evaluation endpoint. ### sendFeedback method override The native `sendFeedback` method from the base controller has been modified to use the evaluation endpoint of the answerApi Co-authored-by: Danny Gauthier --- .../src/api/knowledge/answer-slice.ts | 10 ++- .../api/knowledge/post-answer-evaluation.ts | 31 ++++++++ .../src/api/knowledge/stream-answer-api.ts | 11 ++- ...eadless-answerapi-generated-answer.test.ts | 72 ++++++++++++++++--- .../headless-answerapi-generated-answer.ts | 62 +++++++++++++++- 5 files changed, 172 insertions(+), 14 deletions(-) create mode 100644 packages/headless/src/api/knowledge/post-answer-evaluation.ts diff --git a/packages/headless/src/api/knowledge/answer-slice.ts b/packages/headless/src/api/knowledge/answer-slice.ts index 362293b4142..d9c58fba92d 100644 --- a/packages/headless/src/api/knowledge/answer-slice.ts +++ b/packages/headless/src/api/knowledge/answer-slice.ts @@ -6,9 +6,12 @@ import { FetchBaseQueryError, retry, } from '@reduxjs/toolkit/query'; -import {ConfigurationSection} from '../../state/state-sections'; +import { + ConfigurationSection, + GeneratedAnswerSection, +} from '../../state/state-sections'; -type StateNeededByAnswerSlice = ConfigurationSection; +type StateNeededByAnswerSlice = ConfigurationSection & GeneratedAnswerSection; /** * `dynamicBaseQuery` is passed to the baseQuery of the createApi, @@ -21,6 +24,7 @@ const dynamicBaseQuery: BaseQueryFn< > = async (args, api, extraOptions) => { const state = api.getState() as StateNeededByAnswerSlice; const {accessToken, organizationId, platformUrl} = state.configuration; + const answerConfigurationId = state.generatedAnswer.answerConfigurationId; const updatedArgs = { ...(args as FetchArgs), headers: { @@ -30,7 +34,7 @@ const dynamicBaseQuery: BaseQueryFn< }; try { const data = fetchBaseQuery({ - baseUrl: `${platformUrl}/rest/organizations/${organizationId}`, + baseUrl: `${platformUrl}/rest/organizations/${organizationId}/answer/v1/configs/${answerConfigurationId}`, })(updatedArgs, api, extraOptions); return {data}; } catch (error) { diff --git a/packages/headless/src/api/knowledge/post-answer-evaluation.ts b/packages/headless/src/api/knowledge/post-answer-evaluation.ts new file mode 100644 index 00000000000..1b32e13e446 --- /dev/null +++ b/packages/headless/src/api/knowledge/post-answer-evaluation.ts @@ -0,0 +1,31 @@ +import {answerSlice} from './answer-slice'; + +export interface AnswerEvaluationPOSTParams { + question: string; + helpful: boolean; + answer: { + responseId: string; + text: string; + format: string; + }; + details: { + readable: Boolean | null; + documented: Boolean | null; + correctTopic: Boolean | null; + hallucinationFree: Boolean | null; + }; + correctAnswerUrl: string | null; + additionalNotes: string | null; +} + +export const answerEvaluation = answerSlice.injectEndpoints({ + endpoints: (builder) => ({ + post: builder.mutation({ + query: (body) => ({ + url: '/evaluations', + method: 'POST', + body, + }), + }), + }), +}); diff --git a/packages/headless/src/api/knowledge/stream-answer-api.ts b/packages/headless/src/api/knowledge/stream-answer-api.ts index 9a23fe96139..fc0197a9dc4 100644 --- a/packages/headless/src/api/knowledge/stream-answer-api.ts +++ b/packages/headless/src/api/knowledge/stream-answer-api.ts @@ -32,7 +32,8 @@ export type StateNeededByAnswerAPI = { DebugSection & GeneratedAnswerSection; -interface GeneratedAnswerStream { +export interface GeneratedAnswerStream { + answerId?: string; answerStyle?: GeneratedAnswerStyle; contentFormat?: GeneratedContentFormat; answer?: string; @@ -196,6 +197,14 @@ export const answerApi = answerSlice.injectEndpoints({ 'Accept-Encoding': '*', }, fetch, + onopen: async (res) => { + const answerId = res.headers.get('x-answer-id'); + if (answerId) { + updateCachedData((draft) => { + draft.answerId = answerId; + }); + } + }, onmessage: (event) => { updateCachedData((draft) => { updateCacheWithEvent(event, draft); diff --git a/packages/headless/src/controllers/knowledge/generated-answer/headless-answerapi-generated-answer.test.ts b/packages/headless/src/controllers/knowledge/generated-answer/headless-answerapi-generated-answer.test.ts index ccb912a372c..dfc4695b63a 100644 --- a/packages/headless/src/controllers/knowledge/generated-answer/headless-answerapi-generated-answer.test.ts +++ b/packages/headless/src/controllers/knowledge/generated-answer/headless-answerapi-generated-answer.test.ts @@ -1,14 +1,19 @@ -import {answerApi, fetchAnswer} from '../../../api/knowledge/stream-answer-api'; +import {answerEvaluation} from '../../../api/knowledge/post-answer-evaluation'; +import { + answerApi, + fetchAnswer, + StateNeededByAnswerAPI, +} from '../../../api/knowledge/stream-answer-api'; import { resetAnswer, updateAnswerConfigurationId, updateResponseFormat, } from '../../../features/generated-answer/generated-answer-actions'; -import {generatedAnswerAnalyticsClient} from '../../../features/generated-answer/generated-answer-analytics-actions'; import { - GeneratedAnswerState, - getGeneratedAnswerInitialState, -} from '../../../features/generated-answer/generated-answer-state'; + generatedAnswerAnalyticsClient, + GeneratedAnswerFeedbackV2, +} from '../../../features/generated-answer/generated-answer-analytics-actions'; +import {getGeneratedAnswerInitialState} from '../../../features/generated-answer/generated-answer-state'; import {queryReducer} from '../../../features/query/query-slice'; import { buildMockSearchEngine, @@ -33,8 +38,20 @@ jest.mock('../../../api/knowledge/stream-answer-api', () => { return { ...originalStreamAnswerApi, fetchAnswer: jest.fn(), + selectAnswer: () => ({ + data: {answer: 'This est une answer', answerId: '12345_6'}, + }), }; }); +jest.mock('../../../api/knowledge/post-answer-evaluation', () => ({ + answerEvaluation: { + endpoints: { + post: { + initiate: jest.fn(), + }, + }, + }, +})); describe('knowledge-generated-answer', () => { it('should be tested', () => { @@ -51,12 +68,12 @@ describe('knowledge-generated-answer', () => { ); const buildEngineWithGeneratedAnswer = ( - initialState: Partial = {} + initialState: Partial = {} ) => { const state = createMockState({ + ...initialState, generatedAnswer: { ...getGeneratedAnswerInitialState(), - ...initialState, }, }); return buildMockSearchEngine(state); @@ -93,9 +110,9 @@ describe('knowledge-generated-answer', () => { expect(generatedAnswer.state).toEqual({ ...engine.state.generatedAnswer, - answer: undefined, + answer: 'This est une answer', answerContentFormat: 'text/plain', - error: {message: undefined}, + error: {message: undefined, statusCode: undefined}, }); }); @@ -126,4 +143,41 @@ describe('knowledge-generated-answer', () => { generatedAnswer.reset(); expect(resetAnswer).toHaveBeenCalledTimes(1); }); + + it('dispatches a sendFeedback action', () => { + engine = buildEngineWithGeneratedAnswer({ + query: {q: 'this est une question', enableQuerySyntax: false}, + }); + const generatedAnswer = createGeneratedAnswer(); + const feedback: GeneratedAnswerFeedbackV2 = { + readable: 'unknown', + correctTopic: 'unknown', + documented: 'yes', + hallucinationFree: 'no', + helpful: false, + details: 'some details', + }; + const expectedArgs = { + additionalNotes: 'some details', + answer: { + format: 'text/plain', + responseId: '12345_6', + text: 'This est une answer', + }, + correctAnswerUrl: null, + details: { + correctTopic: null, + documented: true, + hallucinationFree: false, + readable: null, + }, + helpful: false, + question: 'this est une question', + }; + generatedAnswer.sendFeedback(feedback); + expect(answerEvaluation.endpoints.post.initiate).toHaveBeenCalledTimes(1); + expect(answerEvaluation.endpoints.post.initiate).toHaveBeenCalledWith( + expectedArgs + ); + }); }); diff --git a/packages/headless/src/controllers/knowledge/generated-answer/headless-answerapi-generated-answer.ts b/packages/headless/src/controllers/knowledge/generated-answer/headless-answerapi-generated-answer.ts index f996694effd..24c77370efa 100644 --- a/packages/headless/src/controllers/knowledge/generated-answer/headless-answerapi-generated-answer.ts +++ b/packages/headless/src/controllers/knowledge/generated-answer/headless-answerapi-generated-answer.ts @@ -1,6 +1,11 @@ +import { + answerEvaluation, + AnswerEvaluationPOSTParams, +} from '../../../api/knowledge/post-answer-evaluation'; import { answerApi, fetchAnswer, + GeneratedAnswerStream, selectAnswer, selectAnswerTriggerParams, StateNeededByAnswerAPI, @@ -10,6 +15,7 @@ import { resetAnswer, updateAnswerConfigurationId, } from '../../../features/generated-answer/generated-answer-actions'; +import {GeneratedAnswerFeedbackV2} from '../../../features/generated-answer/generated-answer-analytics-actions'; import {queryReducer as query} from '../../../features/query/query-slice'; import { GeneratedAnswerSection, @@ -24,11 +30,17 @@ import { GeneratedResponseFormat, } from '../../core/generated-answer/headless-core-generated-answer'; -export interface AnswerApiGeneratedAnswer extends GeneratedAnswer { +export interface AnswerApiGeneratedAnswer + extends Omit { /** * Resets the last answer. */ reset(): void; + /** + * Sends feedback about why the generated answer was not relevant. + * @param feedback - The feedback that the end user wishes to send. + */ + sendFeedback(feedback: GeneratedAnswerFeedbackV2): void; } interface AnswerApiGeneratedAnswerProps extends GeneratedAnswerProps {} @@ -36,6 +48,46 @@ interface AnswerApiGeneratedAnswerProps extends GeneratedAnswerProps {} export interface SearchAPIGeneratedAnswerAnalyticsClient extends GeneratedAnswerAnalyticsClient {} +interface ParseEvaluationArgumentsParams { + feedback: GeneratedAnswerFeedbackV2; + answerApiState: GeneratedAnswerStream; + query: string; +} + +const parseEvaluationDetails = ( + detail: 'yes' | 'no' | 'unknown' +): Boolean | null => { + if (detail === 'yes') { + return true; + } + if (detail === 'no') { + return false; + } + return null; +}; + +const parseEvaluationArguments = ({ + answerApiState, + feedback, + query, +}: ParseEvaluationArgumentsParams): AnswerEvaluationPOSTParams => ({ + additionalNotes: feedback.details ?? null, + answer: { + text: answerApiState.answer!, + responseId: answerApiState.answerId!, + format: answerApiState.contentFormat ?? 'text/plain', + }, + correctAnswerUrl: feedback.documentUrl ?? null, + details: { + correctTopic: parseEvaluationDetails(feedback.correctTopic), + documented: parseEvaluationDetails(feedback.documented), + hallucinationFree: parseEvaluationDetails(feedback.hallucinationFree), + readable: parseEvaluationDetails(feedback.readable), + }, + helpful: feedback.helpful, + question: query, +}); + const subscribeToSearchRequest = ( engine: SearchEngine ) => { @@ -113,6 +165,14 @@ export function buildAnswerApiGeneratedAnswer( reset() { engine.dispatch(resetAnswer()); }, + async sendFeedback(feedback) { + const args = parseEvaluationArguments({ + query: getState().query.q, + feedback, + answerApiState: selectAnswer(engine.state).data!, + }); + engine.dispatch(answerEvaluation.endpoints.post.initiate(args)); + }, }; } From d97b04de97e3b0fb45943249e330054179ff892a Mon Sep 17 00:00:00 2001 From: Felix Perron-Brault Date: Wed, 31 Jul 2024 18:00:37 -0400 Subject: [PATCH 08/23] fix(atomic): clear min/max for atomic-timeframe-facet when clearing filters (#4240) This PR ensures that the min/max attributes of the `atomic-timeframe-facet` date input get reset correctly when clearing the filters. https://coveord.atlassian.net/browse/KIT-3448 --- .../facet-date-input/facet-date-input.tsx | 10 +- .../atomic-timeframe-facet.new.stories.tsx | 11 +- .../e2e/atomic-timeframe-facet.e2e.ts | 180 ++++++++++++++++++ .../atomic-timeframe-facet/e2e/fixture.ts | 19 ++ .../atomic-timeframe-facet/e2e/page-object.ts | 38 ++++ 5 files changed, 256 insertions(+), 2 deletions(-) create mode 100644 packages/atomic/src/components/search/facets/atomic-timeframe-facet/e2e/atomic-timeframe-facet.e2e.ts create mode 100644 packages/atomic/src/components/search/facets/atomic-timeframe-facet/e2e/fixture.ts create mode 100644 packages/atomic/src/components/search/facets/atomic-timeframe-facet/e2e/page-object.ts diff --git a/packages/atomic/src/components/common/facets/facet-date-input/facet-date-input.tsx b/packages/atomic/src/components/common/facets/facet-date-input/facet-date-input.tsx index 51411d06444..af716a8939b 100644 --- a/packages/atomic/src/components/common/facets/facet-date-input/facet-date-input.tsx +++ b/packages/atomic/src/components/common/facets/facet-date-input/facet-date-input.tsx @@ -40,6 +40,14 @@ export class FacetDateInput { this.start = range ? parseDate(range.start).toDate() : undefined; this.end = range ? parseDate(range.end).toDate() : undefined; } + public componentDidUpdate() { + if (!this.startRef.value && !this.endRef.value) { + this.startRef.min = this.min || this.formattedDateValue('1401-01-01'); + this.endRef.max = this.max || ''; + this.startRef.max = this.max || ''; + this.endRef.min = this.min || ''; + } + } private apply() { if (!this.startRef.validity.valid || !this.endRef.validity.valid) { @@ -138,7 +146,7 @@ export class FacetDateInput { placeholder={placeholder} pattern={pattern} required - min={this.min || this.formattedDateValue(this.start)} + min={this.formattedDateValue(this.start) || this.min} max={this.max} value={this.formattedDateValue(range?.end)} onInput={(e) => diff --git a/packages/atomic/src/components/search/facets/atomic-timeframe-facet/atomic-timeframe-facet.new.stories.tsx b/packages/atomic/src/components/search/facets/atomic-timeframe-facet/atomic-timeframe-facet.new.stories.tsx index 8840de515c3..77df4180cb2 100644 --- a/packages/atomic/src/components/search/facets/atomic-timeframe-facet/atomic-timeframe-facet.new.stories.tsx +++ b/packages/atomic/src/components/search/facets/atomic-timeframe-facet/atomic-timeframe-facet.new.stories.tsx @@ -10,7 +10,16 @@ const meta: Meta = { component: 'atomic-timeframe-facet', title: 'Atomic/TimeframeFacet', id: 'atomic-timeframe-facet', - + argTypes: { + 'attributes-min': { + name: 'min', + type: 'string', + }, + 'attributes-max': { + name: 'max', + type: 'string', + }, + }, render: renderComponent, decorators: [decorator], parameters, diff --git a/packages/atomic/src/components/search/facets/atomic-timeframe-facet/e2e/atomic-timeframe-facet.e2e.ts b/packages/atomic/src/components/search/facets/atomic-timeframe-facet/e2e/atomic-timeframe-facet.e2e.ts new file mode 100644 index 00000000000..7bff8b217c4 --- /dev/null +++ b/packages/atomic/src/components/search/facets/atomic-timeframe-facet/e2e/atomic-timeframe-facet.e2e.ts @@ -0,0 +1,180 @@ +import {test, expect} from './fixture'; + +test.describe('default', () => { + test.beforeEach(async ({facet}) => { + await facet.load({ + args: {withDatePicker: true}, + }); + }); + + test.describe('when selecting a start date', () => { + test.beforeEach(async ({facet}) => { + await facet.facetInputStart.click(); + await facet.facetInputStart.fill('2021-01-01'); + }); + + test('should limit the end date to the selected start date', async ({ + facet, + }) => { + await facet.facetInputEnd.click(); + await expect(facet.facetInputEnd).toHaveAttribute('min', '2021-01-01'); + }); + }); + + test.describe('when selecting an end date', () => { + test.beforeEach(async ({facet}) => { + await facet.facetInputEnd.click(); + await facet.facetInputEnd.fill('2021-01-01'); + }); + + test('should limit the start date to the selected end date', async ({ + facet, + }) => { + await facet.facetInputStart.click(); + await expect(facet.facetInputStart).toHaveAttribute('max', '2021-01-01'); + }); + }); + + test.describe('when selecting a start date and an end date', () => { + test.beforeEach(async ({facet}) => { + await facet.facetInputStart.click(); + await facet.facetInputStart.fill('2021-01-01'); + await facet.facetInputEnd.click(); + await facet.facetInputEnd.fill('2021-01-31'); + }); + + test.describe('when clicking the apply button', () => { + test.beforeEach(async ({facet}) => { + await facet.facetApplyButton.click(); + await facet.facetClearFilter.waitFor({state: 'visible'}); + }); + + test.describe('when clicking the clear filter button', () => { + test.beforeEach(async ({facet}) => { + await facet.facetClearFilter.click(); + }); + + test('should clear the selected dates', async ({facet}) => { + await expect(facet.facetInputStart).toHaveValue(''); + await expect(facet.facetInputEnd).toHaveValue(''); + }); + + test('should reset the min and max values', async ({facet}) => { + await facet.facetInputStart.click(); + await expect(facet.facetInputStart).toHaveAttribute( + 'min', + '1401-01-01' + ); + await facet.facetInputEnd.click(); + await expect(facet.facetInputEnd).toHaveAttribute('max', ''); + }); + + test('should hide the clear filter button', async ({facet}) => { + await expect(facet.facetClearFilter).not.toBeVisible(); + }); + }); + }); + }); + + test('should limit the start date to the default min value', async ({ + facet, + }) => { + await facet.facetInputStart.click(); + await expect(facet.facetInputStart).toHaveAttribute('min', '1401-01-01'); + }); + + test('should not limit the end date', async ({facet}) => { + await facet.facetInputEnd.click(); + await expect(facet.facetInputEnd).not.toHaveAttribute('max'); + }); +}); + +test.describe('with min and max values', () => { + test.beforeEach(async ({facet}) => { + await facet.load({ + args: {withDatePicker: true, min: '2021-01-01', max: '2021-01-31'}, + }); + }); + + test.describe('when selecting a start date', () => { + test.beforeEach(async ({facet}) => { + await facet.facetInputStart.click(); + await facet.facetInputStart.fill('2021-01-01'); + }); + + test('should limit the end date to the selected start date', async ({ + facet, + }) => { + await facet.facetInputEnd.click(); + await expect(facet.facetInputEnd).toHaveAttribute('min', '2021-01-01'); + }); + }); + + test.describe('when selecting an end date', () => { + test.beforeEach(async ({facet}) => { + await facet.facetInputEnd.click(); + await facet.facetInputEnd.fill('2021-01-01'); + }); + + test('should limit the start date to the selected end date', async ({ + facet, + }) => { + await facet.facetInputStart.click(); + await expect(facet.facetInputStart).toHaveAttribute('max', '2021-01-01'); + }); + }); + + test.describe('when selecting a start date and an end date', () => { + test.beforeEach(async ({facet}) => { + await facet.facetInputStart.click(); + await facet.facetInputStart.fill('2021-01-01'); + await facet.facetInputEnd.click(); + await facet.facetInputEnd.fill('2021-01-31'); + }); + + test.describe('when clicking the apply button', () => { + test.beforeEach(async ({facet}) => { + await facet.facetApplyButton.click(); + await facet.facetClearFilter.waitFor({state: 'visible'}); + }); + + test.describe('when clicking the clear filter button', () => { + test.beforeEach(async ({facet}) => { + await facet.facetClearFilter.click(); + }); + + test('should clear the selected dates', async ({facet}) => { + await expect(facet.facetInputStart).toHaveValue(''); + await expect(facet.facetInputEnd).toHaveValue(''); + }); + + test('should reset the min and max values', async ({facet}) => { + await facet.facetInputStart.click(); + await expect(facet.facetInputStart).toHaveAttribute( + 'min', + '2021-01-01' + ); + await facet.facetInputEnd.click(); + await expect(facet.facetInputEnd).toHaveAttribute( + 'max', + '2021-01-31' + ); + }); + + test('should hide the clear filter button', async ({facet}) => { + await expect(facet.facetClearFilter).not.toBeVisible(); + }); + }); + }); + }); + + test('should limit the start date to the min value', async ({facet}) => { + await facet.facetInputStart.click(); + await expect(facet.facetInputStart).toHaveAttribute('min', '2021-01-01'); + }); + + test('should limit the end date to the max value', async ({facet}) => { + await facet.facetInputEnd.click(); + await expect(facet.facetInputEnd).toHaveAttribute('max', '2021-01-31'); + }); +}); diff --git a/packages/atomic/src/components/search/facets/atomic-timeframe-facet/e2e/fixture.ts b/packages/atomic/src/components/search/facets/atomic-timeframe-facet/e2e/fixture.ts new file mode 100644 index 00000000000..7c58d034306 --- /dev/null +++ b/packages/atomic/src/components/search/facets/atomic-timeframe-facet/e2e/fixture.ts @@ -0,0 +1,19 @@ +import {test as base} from '@playwright/test'; +import { + AxeFixture, + makeAxeBuilder, +} from '../../../../../../playwright-utils/base-fixture'; +import {AtomicTimeframeFacetPageObject as Facet} from './page-object'; + +type MyFixture = { + facet: Facet; +}; + +export const test = base.extend({ + makeAxeBuilder, + facet: async ({page}, use) => { + await use(new Facet(page)); + }, +}); + +export {expect} from '@playwright/test'; diff --git a/packages/atomic/src/components/search/facets/atomic-timeframe-facet/e2e/page-object.ts b/packages/atomic/src/components/search/facets/atomic-timeframe-facet/e2e/page-object.ts new file mode 100644 index 00000000000..8c2a56e772b --- /dev/null +++ b/packages/atomic/src/components/search/facets/atomic-timeframe-facet/e2e/page-object.ts @@ -0,0 +1,38 @@ +import type {Page} from '@playwright/test'; +import {BasePageObject} from '../../../../../../playwright-utils/base-page-object'; + +export class AtomicTimeframeFacetPageObject extends BasePageObject<'atomic-timeframe-facet'> { + constructor(page: Page) { + super(page, 'atomic-timeframe-facet'); + } + + get getFacetSearch() { + return this.page.getByLabel('Search'); + } + + get facetInputStart() { + return this.page.getByLabel('Enter a start date for the No label facet'); + } + + get facetInputEnd() { + return this.page.getByLabel('Enter an end date for the No label facet'); + } + + get facetApplyButton() { + return this.page.getByRole('button', { + name: 'Apply custom start and end dates for the No label facet', + }); + } + + get facetClearFilter() { + return this.page.getByRole('button').filter({hasText: 'Clear filter'}); + } + + get getFacetValue() { + return this.page.locator('[part="value-box"]'); + } + + get facetSearchMoreMatchesFor() { + return this.page.getByRole('button', {name: 'More matches for p'}); + } +} From 35e7f177f9558bc05e4cd91f62a08ecb3accc7f3 Mon Sep 17 00:00:00 2001 From: Alex Prudhomme <78121423+alexprudhomme@users.noreply.github.com> Date: Wed, 31 Jul 2024 18:38:45 -0400 Subject: [PATCH 09/23] ci: split facet cypress tests in half (#4237) https://coveord.atlassian.net/browse/KIT-3445 gotta go fast. Slowest action went from 18 to 11 min. Closer to the second slowest at 8 mins. ![3aa5921d82d2ac608b8c575ec87ee2ac](https://github.com/user-attachments/assets/ee8efc45-4c1f-4198-9c38-2a53b950673e) --- .../{facet.cypress.ts => facet.1.cypress.ts} | 622 +---------------- .../e2e/facets/facet/facet.2.cypress.ts | 630 ++++++++++++++++++ 2 files changed, 633 insertions(+), 619 deletions(-) rename packages/atomic/cypress/e2e/facets/facet/{facet.cypress.ts => facet.1.cypress.ts} (50%) create mode 100644 packages/atomic/cypress/e2e/facets/facet/facet.2.cypress.ts diff --git a/packages/atomic/cypress/e2e/facets/facet/facet.cypress.ts b/packages/atomic/cypress/e2e/facets/facet/facet.1.cypress.ts similarity index 50% rename from packages/atomic/cypress/e2e/facets/facet/facet.cypress.ts rename to packages/atomic/cypress/e2e/facets/facet/facet.1.cypress.ts index bf8ad4d4039..688cd70387f 100644 --- a/packages/atomic/cypress/e2e/facets/facet/facet.cypress.ts +++ b/packages/atomic/cypress/e2e/facets/facet/facet.1.cypress.ts @@ -1,24 +1,12 @@ -import {FacetValue} from '@coveo/headless'; -import {SearchInterface, TestFixture} from '../../../fixtures/test-fixture'; +import {TestFixture} from '../../../fixtures/test-fixture'; import {AnalyticsTracker} from '../../../utils/analyticsUtils'; -import { - addBreadbox, - breadboxLabel, - deselectBreadcrumbAtIndex, -} from '../../breadbox-actions'; -import * as BreadboxAssertions from '../../breadbox-assertions'; -import {breadboxComponent, BreadboxSelectors} from '../../breadbox-selectors'; import * as CommonAssertions from '../../common-assertions'; import { pressClearButton, - pressLabelButton, - pressShowLess, - pressShowMore, selectIdleCheckboxValueAt, selectIdleLinkValueAt, typeFacetSearchQuery, pressClearSearchButton, - excludeIdleCheckboxValueAt, } from '../facet-common-actions'; import * as CommonFacetAssertions from '../facet-common-assertions'; import { @@ -31,7 +19,8 @@ import { import * as FacetAssertions from './facet-assertions'; import {facetComponent, FacetSelectors} from './facet-selectors'; -describe('Facet v1 Test Suites', () => { +// This is the first half of the facet test suite. It was split in two to speed up the test execution. +describe('Facet Test Suite 1', () => { describe('with checkbox values', () => { function setupWithCheckboxValues() { new TestFixture().with(addFacet({field, label})).init(); @@ -641,609 +630,4 @@ describe('Facet v1 Test Suites', () => { }); }); }); - - describe('when selecting the "Show more" button', () => { - function setupSelectShowMore(sortCriteria?: string) { - new TestFixture() - .with( - addFacet({ - field, - label, - ...(sortCriteria && {'sort-criteria': sortCriteria}), - }) - ) - .init(); - pressShowMore(FacetSelectors); - } - - describe('verify rendering', () => { - beforeEach(() => setupSelectShowMore('automatic')); - - CommonFacetAssertions.assertDisplayShowMoreButton(FacetSelectors, true); - CommonFacetAssertions.assertDisplayShowLessButton(FacetSelectors, true); - FacetAssertions.assertValuesSortedAlphanumerically(); - CommonFacetAssertions.assertNumberOfIdleCheckboxValues( - FacetSelectors, - defaultNumberOfValues * 2 - ); - CommonFacetAssertions.assertFocusCheckboxValue(FacetSelectors, 0); - }); - - describe("when the sort order isn't automatic", () => { - beforeEach(() => setupSelectShowMore('alphanumeric')); - - CommonFacetAssertions.assertFocusCheckboxValue( - FacetSelectors, - defaultNumberOfValues - ); - }); - - describe('verify analytics', () => { - beforeEach(() => setupSelectShowMore()); - - FacetAssertions.assertLogFacetShowMore(field); - it('should include analytics in the v2 call', async () => { - cy.wait(TestFixture.interceptAliases.Search).should((firstSearch) => { - expect(firstSearch.request.body).to.have.property('analytics'); - const analyticsBody = firstSearch.request.body.analytics; - expect(analyticsBody).to.have.property('eventType', 'facet'); - expect(analyticsBody).to.have.property( - 'eventValue', - 'showMoreFacetResults' - ); - expect(analyticsBody.customData).to.have.property( - 'facetField', - field - ); - }); - }); - }); - - describe('when there\'s no more "Show more" button', () => { - function setupRepeatShowMore() { - new TestFixture().with(addFacet({field: 'month', label})).init(); - FacetSelectors.showMoreButton().click(); - cy.wait(TestFixture.interceptAliases.Search); - } - beforeEach(setupRepeatShowMore); - - describe('verify rendering', () => { - CommonFacetAssertions.assertDisplayShowMoreButton( - FacetSelectors, - false - ); - CommonFacetAssertions.assertDisplayShowLessButton(FacetSelectors, true); - }); - }); - - describe('when selecting the "Show less" button', () => { - function setupSelectShowLess() { - setupSelectShowMore(); - pressShowLess(FacetSelectors); - } - beforeEach(setupSelectShowLess); - - describe('verify rendering', () => { - CommonFacetAssertions.assertDisplayShowMoreButton(FacetSelectors, true); - CommonFacetAssertions.assertDisplayShowLessButton( - FacetSelectors, - false - ); - CommonFacetAssertions.assertNumberOfIdleCheckboxValues( - FacetSelectors, - defaultNumberOfValues - ); - }); - - describe('verify analytics', () => { - FacetAssertions.assertLogFacetShowLess(field); - }); - }); - }); - - describe('when selecting the label button to collapse', () => { - function setupSelectLabelCollapse() { - new TestFixture().with(addFacet({field, label})).init(); - selectIdleCheckboxValueAt(FacetSelectors, 0); - pressLabelButton(FacetSelectors, true); - } - - beforeEach(setupSelectLabelCollapse); - - CommonAssertions.assertAccessibility(facetComponent); - CommonAssertions.assertContainsComponentError(FacetSelectors, false); - CommonFacetAssertions.assertDisplayFacet(FacetSelectors, true); - CommonFacetAssertions.assertDisplayClearButton(FacetSelectors, true); - CommonFacetAssertions.assertDisplaySearchInput(FacetSelectors, false); - CommonFacetAssertions.assertDisplayValues(FacetSelectors, false); - CommonFacetAssertions.assertDisplayShowMoreButton( - FacetSelectors, - false, - false - ); - CommonFacetAssertions.assertDisplayShowLessButton( - FacetSelectors, - false, - false - ); - CommonFacetAssertions.assertLabelContains(FacetSelectors, label); - - describe('when selecting the label button to expand', () => { - function setupSelectLabelExpand() { - FacetSelectors.labelButton().click(); - } - - beforeEach(setupSelectLabelExpand); - - CommonFacetAssertions.assertDisplayClearButton(FacetSelectors, true); - CommonFacetAssertions.assertDisplaySearchInput(FacetSelectors, true); - CommonFacetAssertions.assertDisplayValues(FacetSelectors, true); - CommonFacetAssertions.assertDisplayShowMoreButton(FacetSelectors, true); - }); - }); - - describe('with custom #numberOfValues', () => { - const numberOfValues = 2; - function setupCustomNumberOfValues() { - new TestFixture() - .with(addFacet({field, label, 'number-of-values': numberOfValues})) - .init(); - } - - beforeEach(setupCustomNumberOfValues); - - CommonFacetAssertions.assertNumberOfIdleCheckboxValues( - FacetSelectors, - numberOfValues - ); - CommonFacetAssertions.assertDisplayShowMoreButton(FacetSelectors, true); - CommonFacetAssertions.assertDisplayShowLessButton(FacetSelectors, false); - - describe('when selecting the "Show More" button', () => { - beforeEach(() => { - pressShowMore(FacetSelectors); - }); - - CommonFacetAssertions.assertNumberOfIdleCheckboxValues( - FacetSelectors, - numberOfValues * 2 - ); - CommonFacetAssertions.assertDisplayShowMoreButton(FacetSelectors, true); - CommonFacetAssertions.assertDisplayShowLessButton(FacetSelectors, true); - }); - }); - - describe('with custom #sortCriteria, alphanumeric', () => { - beforeEach(() => { - new TestFixture() - .with(addFacet({field, label, 'sort-criteria': 'alphanumeric'})) - .init(); - }); - - FacetAssertions.assertValuesSortedAlphanumerically(); - }); - - describe('with custom #sortCriteria, alphanumericDescending', () => { - beforeEach(() => { - new TestFixture() - .with( - addFacet({field, label, 'sort-criteria': 'alphanumericDescending'}) - ) - .init(); - }); - - FacetAssertions.assertValuesSortedAlphanumericallyDescending(); - }); - - describe('with custom #sortCriteria, occurrences', () => { - beforeEach(() => { - new TestFixture() - .with(addFacet({field, label, 'sort-criteria': 'occurrences'})) - .init(); - }); - - FacetAssertions.assertValuesSortedByOccurrences(); - }); - - describe('with #resultsMustMatch set to "allValues"', () => { - function setupSelectCheckboxValue() { - new TestFixture() - .with(addFacet({field, label, 'results-must-match': 'allValues'})) - .init(); - selectIdleCheckboxValueAt(FacetSelectors, 0); - } - - beforeEach(() => { - setupSelectCheckboxValue(); - }); - - it('should set resultsMustMatch to `allValues`', () => { - cy.wait(TestFixture.interceptAliases.Search).should((search) => { - expect(search.request.body.facets[0]).to.have.property( - 'resultsMustMatch', - 'allValues' - ); - }); - }); - }); - - describe('with #resultsMustMatch set to default value', () => { - function setupSelectCheckboxValue() { - new TestFixture().with(addFacet({field, label})).init(); - selectIdleCheckboxValueAt(FacetSelectors, 0); - } - - beforeEach(() => { - setupSelectCheckboxValue(); - }); - - it('should set resultsMustMatch to `atLeastOneValue`', () => { - cy.wait(TestFixture.interceptAliases.Search).should((search) => { - expect(search.request.body.facets[0]).to.have.property( - 'resultsMustMatch', - 'atLeastOneValue' - ); - }); - }); - }); - - describe('when defining a value caption', () => { - const caption = 'nicecaption'; - beforeEach(() => { - const fixture = new TestFixture().with(addFacet({field, label})).init(); - cy.get(`@${fixture.elementAliases.SearchInterface}`).then(($si) => { - const searchInterfaceComponent = $si.get()[0] as SearchInterface; - - searchInterfaceComponent.i18n.addResource( - 'en', - `caption-${field}`, - 'People', - caption - ); - }); - - typeFacetSearchQuery(FacetSelectors, caption, true); - }); - - CommonFacetAssertions.assertFirstValueContains(FacetSelectors, caption); - }); - - describe('with #withSearch to false', () => { - beforeEach(() => { - new TestFixture() - .with(addFacet({field, label, 'with-search': 'false'})) - .init(); - }); - - CommonFacetAssertions.assertDisplayFacet(FacetSelectors, true); - CommonFacetAssertions.assertDisplaySearchInput(FacetSelectors, false); - }); - - describe('with #withSearch to true and expanded (moreValuesAvailable=false)', () => { - const setup = (numValues: number) => - new TestFixture() - .with(addFacet({field, label, 'with-search': 'true'})) - .withCustomResponse((response) => { - response.facets[0].values = [...Array(numValues).keys()].map((i) => { - return {value: i.toString(), numberOfResults: 1}; - }) as FacetValue[]; - response.facets[0].moreValuesAvailable = false; - }) - .init(); - - it('with less than 8 values, it should not display the search input', () => { - setup(3); - CommonFacetAssertions.assertDisplaySearchInputWithoutIt( - FacetSelectors, - false - ); - }); - - it('with exactly 8 values, it should not display the search input', () => { - setup(8); - CommonFacetAssertions.assertDisplaySearchInputWithoutIt( - FacetSelectors, - false - ); - }); - - it('with more than 8 values, it should display the search input', () => { - setup(10); - CommonFacetAssertions.assertDisplaySearchInputWithoutIt( - FacetSelectors, - true - ); - }); - }); - - describe('when no search has yet been executed', () => { - beforeEach(() => { - new TestFixture() - .with(addFacet({field, label})) - .withoutFirstAutomaticSearch() - .init(); - }); - - CommonFacetAssertions.assertDisplayFacet(FacetSelectors, false); - CommonFacetAssertions.assertDisplayPlaceholder(FacetSelectors, true); - }); - - describe('with an invalid option', () => { - beforeEach(() => { - new TestFixture() - .with(addFacet({field, label, 'sort-criteria': 'nononono'})) - .init(); - }); - - CommonFacetAssertions.assertDisplayFacet(FacetSelectors, false); - CommonAssertions.assertContainsComponentError(FacetSelectors, true); - }); - - describe('when field returns no results', () => { - beforeEach(() => { - new TestFixture() - .with(addFacet({field: 'notanactualfield', label})) - .init(); - }); - - CommonFacetAssertions.assertDisplayFacet(FacetSelectors, false); - CommonFacetAssertions.assertDisplayPlaceholder(FacetSelectors, false); - CommonAssertions.assertContainsComponentError(FacetSelectors, false); - }); - - describe('with a selected path in the URL', () => { - beforeEach(() => { - new TestFixture() - .with(addFacet({field, label})) - .withHash(`f-${field}=Cervantes`) - .init(); - }); - - CommonFacetAssertions.assertDisplayFacet(FacetSelectors, true); - CommonFacetAssertions.assertNumberOfSelectedCheckboxValues( - FacetSelectors, - 1 - ); - CommonFacetAssertions.assertNumberOfIdleCheckboxValues( - FacetSelectors, - defaultNumberOfValues - 1 - ); - CommonFacetAssertions.assertFirstValueContains(FacetSelectors, 'Cervantes'); - }); - - describe('with breadbox', () => { - function breadboxFactory(enableExclusion: boolean) { - new TestFixture() - .with(addBreadbox()) - .with( - addFacet({field, label, 'enable-exclusion': String(enableExclusion)}) - ) - .init(); - } - - function setupBreadboxWithFacet() { - breadboxFactory(false); - } - - function setupBreadboxWithFacetWithExclusionEnabled() { - breadboxFactory(true); - } - - describe('verify rendering', () => { - beforeEach(setupBreadboxWithFacet); - BreadboxAssertions.assertDisplayBreadcrumb(false); - }); - - describe('when selecting a value', () => { - const selectionIndex = 2; - function setupSelectedFacet() { - setupBreadboxWithFacet(); - selectIdleCheckboxValueAt(FacetSelectors, selectionIndex); - } - - describe('verify rendering', () => { - beforeEach(() => { - setupSelectedFacet(); - cy.wait(TestFixture.interceptAliases.Search); - }); - - CommonAssertions.assertAccessibility(breadboxComponent); - BreadboxAssertions.assertDisplayBreadcrumb(true); - BreadboxAssertions.assertDisplayBreadcrumbClearAllButton(true); - BreadboxAssertions.assertBreadcrumbLabel(breadboxLabel); - BreadboxAssertions.assertSelectedCheckboxFacetsInBreadcrumb( - FacetSelectors - ); - BreadboxAssertions.assertDisplayBreadcrumbClearIcon(); - }); - - describe('when deselecting a facetValue on breadcrumb', () => { - const deselectionIndex = 0; - function setupDeselectFacetValue() { - setupSelectedFacet(); - deselectBreadcrumbAtIndex(deselectionIndex); - } - - describe('verify rendering', () => { - beforeEach(() => { - setupDeselectFacetValue(); - cy.wait(TestFixture.interceptAliases.Search); - }); - BreadboxAssertions.assertDisplayBreadcrumb(false); - }); - - describe('verify analytic', () => { - beforeEach(setupDeselectFacetValue); - BreadboxAssertions.assertLogBreadcrumbFacet(field); - }); - - describe('verify selected facetValue', () => { - beforeEach(setupSelectedFacet); - BreadboxAssertions.assertDeselectCheckboxFacet( - FacetSelectors, - deselectionIndex - ); - }); - }); - }); - - describe('when select 3 values', () => { - const index = [0, 1, 2]; - function setupSelectedMultipleFacets() { - setupBreadboxWithFacet(); - index.forEach((position, i) => { - selectIdleCheckboxValueAt(FacetSelectors, position); - cy.wait(TestFixture.interceptAliases.Search); - BreadboxSelectors.breadcrumbButton().should('have.length', i + 1); - }); - } - - describe('verify rendering', () => { - beforeEach(setupSelectedMultipleFacets); - CommonAssertions.assertAccessibility(breadboxComponent); - BreadboxAssertions.assertDisplayBreadcrumb(true); - BreadboxAssertions.assertDisplayBreadcrumbClearAllButton(true); - BreadboxAssertions.assertBreadcrumbLabel(breadboxLabel); - BreadboxAssertions.assertSelectedCheckboxFacetsInBreadcrumb( - FacetSelectors - ); - BreadboxAssertions.assertDisplayBreadcrumbShowMore(false); - BreadboxAssertions.assertBreadcrumbDisplayLength(index.length); - }); - }); - - describe('when excluding 3 values', () => { - const index = [0, 1, 2]; - function setupSelectedMultipleFacets() { - setupBreadboxWithFacetWithExclusionEnabled(); - index.forEach((position, i) => { - excludeIdleCheckboxValueAt(FacetSelectors, position); - cy.wait(TestFixture.interceptAliases.Search); - BreadboxSelectors.breadcrumbButton().should('have.length', i + 1); - }); - } - - describe('verify rendering', () => { - beforeEach(setupSelectedMultipleFacets); - CommonAssertions.assertAccessibility(breadboxComponent); - BreadboxAssertions.assertDisplayBreadcrumb(true); - BreadboxAssertions.assertDisplayBreadcrumbClearAllButton(true); - BreadboxAssertions.assertBreadcrumbLabel(breadboxLabel); - BreadboxAssertions.assertExcludedCheckboxFacetsInBreadcrumb( - FacetSelectors - ); - BreadboxAssertions.assertDisplayBreadcrumbShowMore(false); - BreadboxAssertions.assertBreadcrumbDisplayLength(index.length); - }); - }); - }); - - describe('with depends-on', () => { - describe('as a dependent & parent', () => { - const facetId = 'abc'; - const parentFacetId = 'def'; - const parentField = 'filetype'; - const expectedValue = 'txt'; - beforeEach(() => { - new TestFixture() - .with( - addFacet({ - 'facet-id': facetId, - field, - [`depends-on-${parentFacetId}`]: expectedValue, - }) - ) - .with(addFacet({'facet-id': parentFacetId, field: parentField})) - .init(); - }); - - it('should control display of both parent and child properly', () => { - FacetSelectors.withId(facetId).wrapper().should('not.exist'); - FacetSelectors.withId(parentFacetId).wrapper().should('be.visible'); - }); - - it('should control the display of both parent and child properly when the dependency is met', () => { - typeFacetSearchQuery( - FacetSelectors.withId(parentFacetId), - expectedValue, - true - ); - selectIdleCheckboxValueAt(FacetSelectors.withId(parentFacetId), 0); - FacetSelectors.withId(facetId).wrapper().should('be.visible'); - FacetSelectors.withId(parentFacetId).wrapper().should('be.visible'); - }); - }); - - describe('with two dependencies', () => { - beforeEach(() => { - new TestFixture() - .with(addFacet({'facet-id': 'abc', field: 'objecttype'})) - .with(addFacet({'facet-id': 'def', field: 'filetype'})) - .with( - addFacet({ - 'facet-id': 'ghi', - field, - 'depends-on-objecttype': '', - 'depends-on-filetype': 'pdf', - }) - ) - .init(); - }); - - CommonAssertions.assertConsoleError(true); - CommonAssertions.assertContainsComponentError( - FacetSelectors.withId('ghi'), - true - ); - }); - }); - - describe('with allowed-values', () => { - beforeEach(() => { - new TestFixture() - .with( - addFacet({ - field: 'objecttype', - 'allowed-values': JSON.stringify(['FAQ', 'People']), - }) - ) - .init(); - }); - - it('returns only allowed values', () => { - FacetSelectors.values() - .should('contain.text', 'FAQ') - .should('contain.text', 'People') - .should('not.contain.text', 'Page'); - }); - }); - - describe('with custom-sort', () => { - beforeEach(() => { - new TestFixture() - .with( - addFacet({ - field: 'filetype', - 'custom-sort': JSON.stringify(['txt', 'rssitem']), - }) - ) - .init(); - }); - - it('returns values sorted in the proper order', () => { - FacetSelectors.valueLabel().eq(0).should('contain.text', 'txt'); - FacetSelectors.valueLabel().eq(1).should('contain.text', 'rssitem'); - }); - }); - - it('should use the Coveo platform defined label when none is provided', () => { - new TestFixture().with(addFacet({field: 'objecttype'})).init(); - // The label Object type is defined in the Coveo administration tool for the organization searchuisamples - FacetSelectors.labelButton().should('contain.text', 'Object type'); - }); - - it('should use No label when none is provided and there is no fallback configured in the Coveo platform', () => { - new TestFixture().with(addFacet({field: 'filetype'})).init(); - FacetSelectors.labelButton().should('contain.text', 'No label'); - }); }); diff --git a/packages/atomic/cypress/e2e/facets/facet/facet.2.cypress.ts b/packages/atomic/cypress/e2e/facets/facet/facet.2.cypress.ts new file mode 100644 index 00000000000..a33d40846ca --- /dev/null +++ b/packages/atomic/cypress/e2e/facets/facet/facet.2.cypress.ts @@ -0,0 +1,630 @@ +import {FacetValue} from '@coveo/headless'; +import {SearchInterface, TestFixture} from '../../../fixtures/test-fixture'; +import { + addBreadbox, + breadboxLabel, + deselectBreadcrumbAtIndex, +} from '../../breadbox-actions'; +import * as BreadboxAssertions from '../../breadbox-assertions'; +import {breadboxComponent, BreadboxSelectors} from '../../breadbox-selectors'; +import * as CommonAssertions from '../../common-assertions'; +import { + pressLabelButton, + pressShowLess, + pressShowMore, + selectIdleCheckboxValueAt, + typeFacetSearchQuery, + excludeIdleCheckboxValueAt, +} from '../facet-common-actions'; +import * as CommonFacetAssertions from '../facet-common-assertions'; +import {addFacet, field, label, defaultNumberOfValues} from './facet-actions'; +import * as FacetAssertions from './facet-assertions'; +import {facetComponent, FacetSelectors} from './facet-selectors'; + +// This is the second half of the facet test suite. It was split in two to speed up the test execution. +describe('Facet Test Suite 2', () => { + describe('when selecting the "Show more" button', () => { + function setupSelectShowMore(sortCriteria?: string) { + new TestFixture() + .with( + addFacet({ + field, + label, + ...(sortCriteria && {'sort-criteria': sortCriteria}), + }) + ) + .init(); + pressShowMore(FacetSelectors); + } + + describe('verify rendering', () => { + beforeEach(() => setupSelectShowMore('automatic')); + + CommonFacetAssertions.assertDisplayShowMoreButton(FacetSelectors, true); + CommonFacetAssertions.assertDisplayShowLessButton(FacetSelectors, true); + FacetAssertions.assertValuesSortedAlphanumerically(); + CommonFacetAssertions.assertNumberOfIdleCheckboxValues( + FacetSelectors, + defaultNumberOfValues * 2 + ); + CommonFacetAssertions.assertFocusCheckboxValue(FacetSelectors, 0); + }); + + describe("when the sort order isn't automatic", () => { + beforeEach(() => setupSelectShowMore('alphanumeric')); + + CommonFacetAssertions.assertFocusCheckboxValue( + FacetSelectors, + defaultNumberOfValues + ); + }); + + describe('verify analytics', () => { + beforeEach(() => setupSelectShowMore()); + + FacetAssertions.assertLogFacetShowMore(field); + it('should include analytics in the v2 call', async () => { + cy.wait(TestFixture.interceptAliases.Search).should((firstSearch) => { + expect(firstSearch.request.body).to.have.property('analytics'); + const analyticsBody = firstSearch.request.body.analytics; + expect(analyticsBody).to.have.property('eventType', 'facet'); + expect(analyticsBody).to.have.property( + 'eventValue', + 'showMoreFacetResults' + ); + expect(analyticsBody.customData).to.have.property( + 'facetField', + field + ); + }); + }); + }); + + describe('when there\'s no more "Show more" button', () => { + function setupRepeatShowMore() { + new TestFixture().with(addFacet({field: 'month', label})).init(); + FacetSelectors.showMoreButton().click(); + cy.wait(TestFixture.interceptAliases.Search); + } + beforeEach(setupRepeatShowMore); + + describe('verify rendering', () => { + CommonFacetAssertions.assertDisplayShowMoreButton( + FacetSelectors, + false + ); + CommonFacetAssertions.assertDisplayShowLessButton(FacetSelectors, true); + }); + }); + + describe('when selecting the "Show less" button', () => { + function setupSelectShowLess() { + setupSelectShowMore(); + pressShowLess(FacetSelectors); + } + beforeEach(setupSelectShowLess); + + describe('verify rendering', () => { + CommonFacetAssertions.assertDisplayShowMoreButton(FacetSelectors, true); + CommonFacetAssertions.assertDisplayShowLessButton( + FacetSelectors, + false + ); + CommonFacetAssertions.assertNumberOfIdleCheckboxValues( + FacetSelectors, + defaultNumberOfValues + ); + }); + + describe('verify analytics', () => { + FacetAssertions.assertLogFacetShowLess(field); + }); + }); + }); + + describe('when selecting the label button to collapse', () => { + function setupSelectLabelCollapse() { + new TestFixture().with(addFacet({field, label})).init(); + selectIdleCheckboxValueAt(FacetSelectors, 0); + pressLabelButton(FacetSelectors, true); + } + + beforeEach(setupSelectLabelCollapse); + + CommonAssertions.assertAccessibility(facetComponent); + CommonAssertions.assertContainsComponentError(FacetSelectors, false); + CommonFacetAssertions.assertDisplayFacet(FacetSelectors, true); + CommonFacetAssertions.assertDisplayClearButton(FacetSelectors, true); + CommonFacetAssertions.assertDisplaySearchInput(FacetSelectors, false); + CommonFacetAssertions.assertDisplayValues(FacetSelectors, false); + CommonFacetAssertions.assertDisplayShowMoreButton( + FacetSelectors, + false, + false + ); + CommonFacetAssertions.assertDisplayShowLessButton( + FacetSelectors, + false, + false + ); + CommonFacetAssertions.assertLabelContains(FacetSelectors, label); + + describe('when selecting the label button to expand', () => { + function setupSelectLabelExpand() { + FacetSelectors.labelButton().click(); + } + + beforeEach(setupSelectLabelExpand); + + CommonFacetAssertions.assertDisplayClearButton(FacetSelectors, true); + CommonFacetAssertions.assertDisplaySearchInput(FacetSelectors, true); + CommonFacetAssertions.assertDisplayValues(FacetSelectors, true); + CommonFacetAssertions.assertDisplayShowMoreButton(FacetSelectors, true); + }); + }); + + describe('with custom #numberOfValues', () => { + const numberOfValues = 2; + function setupCustomNumberOfValues() { + new TestFixture() + .with(addFacet({field, label, 'number-of-values': numberOfValues})) + .init(); + } + + beforeEach(setupCustomNumberOfValues); + + CommonFacetAssertions.assertNumberOfIdleCheckboxValues( + FacetSelectors, + numberOfValues + ); + CommonFacetAssertions.assertDisplayShowMoreButton(FacetSelectors, true); + CommonFacetAssertions.assertDisplayShowLessButton(FacetSelectors, false); + + describe('when selecting the "Show More" button', () => { + beforeEach(() => { + pressShowMore(FacetSelectors); + }); + + CommonFacetAssertions.assertNumberOfIdleCheckboxValues( + FacetSelectors, + numberOfValues * 2 + ); + CommonFacetAssertions.assertDisplayShowMoreButton(FacetSelectors, true); + CommonFacetAssertions.assertDisplayShowLessButton(FacetSelectors, true); + }); + }); + + describe('with custom #sortCriteria, alphanumeric', () => { + beforeEach(() => { + new TestFixture() + .with(addFacet({field, label, 'sort-criteria': 'alphanumeric'})) + .init(); + }); + + FacetAssertions.assertValuesSortedAlphanumerically(); + }); + + describe('with custom #sortCriteria, alphanumericDescending', () => { + beforeEach(() => { + new TestFixture() + .with( + addFacet({field, label, 'sort-criteria': 'alphanumericDescending'}) + ) + .init(); + }); + + FacetAssertions.assertValuesSortedAlphanumericallyDescending(); + }); + + describe('with custom #sortCriteria, occurrences', () => { + beforeEach(() => { + new TestFixture() + .with(addFacet({field, label, 'sort-criteria': 'occurrences'})) + .init(); + }); + + FacetAssertions.assertValuesSortedByOccurrences(); + }); + + describe('with #resultsMustMatch set to "allValues"', () => { + function setupSelectCheckboxValue() { + new TestFixture() + .with(addFacet({field, label, 'results-must-match': 'allValues'})) + .init(); + selectIdleCheckboxValueAt(FacetSelectors, 0); + } + + beforeEach(() => { + setupSelectCheckboxValue(); + }); + + it('should set resultsMustMatch to `allValues`', () => { + cy.wait(TestFixture.interceptAliases.Search).should((search) => { + expect(search.request.body.facets[0]).to.have.property( + 'resultsMustMatch', + 'allValues' + ); + }); + }); + }); + + describe('with #resultsMustMatch set to default value', () => { + function setupSelectCheckboxValue() { + new TestFixture().with(addFacet({field, label})).init(); + selectIdleCheckboxValueAt(FacetSelectors, 0); + } + + beforeEach(() => { + setupSelectCheckboxValue(); + }); + + it('should set resultsMustMatch to `atLeastOneValue`', () => { + cy.wait(TestFixture.interceptAliases.Search).should((search) => { + expect(search.request.body.facets[0]).to.have.property( + 'resultsMustMatch', + 'atLeastOneValue' + ); + }); + }); + }); + + describe('when defining a value caption', () => { + const caption = 'nicecaption'; + beforeEach(() => { + const fixture = new TestFixture().with(addFacet({field, label})).init(); + cy.get(`@${fixture.elementAliases.SearchInterface}`).then(($si) => { + const searchInterfaceComponent = $si.get()[0] as SearchInterface; + + searchInterfaceComponent.i18n.addResource( + 'en', + `caption-${field}`, + 'People', + caption + ); + }); + + typeFacetSearchQuery(FacetSelectors, caption, true); + }); + + CommonFacetAssertions.assertFirstValueContains(FacetSelectors, caption); + }); + + describe('with #withSearch to false', () => { + beforeEach(() => { + new TestFixture() + .with(addFacet({field, label, 'with-search': 'false'})) + .init(); + }); + + CommonFacetAssertions.assertDisplayFacet(FacetSelectors, true); + CommonFacetAssertions.assertDisplaySearchInput(FacetSelectors, false); + }); + + describe('with #withSearch to true and expanded (moreValuesAvailable=false)', () => { + const setup = (numValues: number) => + new TestFixture() + .with(addFacet({field, label, 'with-search': 'true'})) + .withCustomResponse((response) => { + response.facets[0].values = [...Array(numValues).keys()].map((i) => { + return {value: i.toString(), numberOfResults: 1}; + }) as FacetValue[]; + response.facets[0].moreValuesAvailable = false; + }) + .init(); + + it('with less than 8 values, it should not display the search input', () => { + setup(3); + CommonFacetAssertions.assertDisplaySearchInputWithoutIt( + FacetSelectors, + false + ); + }); + + it('with exactly 8 values, it should not display the search input', () => { + setup(8); + CommonFacetAssertions.assertDisplaySearchInputWithoutIt( + FacetSelectors, + false + ); + }); + + it('with more than 8 values, it should display the search input', () => { + setup(10); + CommonFacetAssertions.assertDisplaySearchInputWithoutIt( + FacetSelectors, + true + ); + }); + }); + + describe('when no search has yet been executed', () => { + beforeEach(() => { + new TestFixture() + .with(addFacet({field, label})) + .withoutFirstAutomaticSearch() + .init(); + }); + + CommonFacetAssertions.assertDisplayFacet(FacetSelectors, false); + CommonFacetAssertions.assertDisplayPlaceholder(FacetSelectors, true); + }); + + describe('with an invalid option', () => { + beforeEach(() => { + new TestFixture() + .with(addFacet({field, label, 'sort-criteria': 'nononono'})) + .init(); + }); + + CommonFacetAssertions.assertDisplayFacet(FacetSelectors, false); + CommonAssertions.assertContainsComponentError(FacetSelectors, true); + }); + + describe('when field returns no results', () => { + beforeEach(() => { + new TestFixture() + .with(addFacet({field: 'notanactualfield', label})) + .init(); + }); + + CommonFacetAssertions.assertDisplayFacet(FacetSelectors, false); + CommonFacetAssertions.assertDisplayPlaceholder(FacetSelectors, false); + CommonAssertions.assertContainsComponentError(FacetSelectors, false); + }); + + describe('with a selected path in the URL', () => { + beforeEach(() => { + new TestFixture() + .with(addFacet({field, label})) + .withHash(`f-${field}=Cervantes`) + .init(); + }); + + CommonFacetAssertions.assertDisplayFacet(FacetSelectors, true); + CommonFacetAssertions.assertNumberOfSelectedCheckboxValues( + FacetSelectors, + 1 + ); + CommonFacetAssertions.assertNumberOfIdleCheckboxValues( + FacetSelectors, + defaultNumberOfValues - 1 + ); + CommonFacetAssertions.assertFirstValueContains(FacetSelectors, 'Cervantes'); + }); + + describe('with breadbox', () => { + function breadboxFactory(enableExclusion: boolean) { + new TestFixture() + .with(addBreadbox()) + .with( + addFacet({field, label, 'enable-exclusion': String(enableExclusion)}) + ) + .init(); + } + + function setupBreadboxWithFacet() { + breadboxFactory(false); + } + + function setupBreadboxWithFacetWithExclusionEnabled() { + breadboxFactory(true); + } + + describe('verify rendering', () => { + beforeEach(setupBreadboxWithFacet); + BreadboxAssertions.assertDisplayBreadcrumb(false); + }); + + describe('when selecting a value', () => { + const selectionIndex = 2; + function setupSelectedFacet() { + setupBreadboxWithFacet(); + selectIdleCheckboxValueAt(FacetSelectors, selectionIndex); + } + + describe('verify rendering', () => { + beforeEach(() => { + setupSelectedFacet(); + cy.wait(TestFixture.interceptAliases.Search); + }); + + CommonAssertions.assertAccessibility(breadboxComponent); + BreadboxAssertions.assertDisplayBreadcrumb(true); + BreadboxAssertions.assertDisplayBreadcrumbClearAllButton(true); + BreadboxAssertions.assertBreadcrumbLabel(breadboxLabel); + BreadboxAssertions.assertSelectedCheckboxFacetsInBreadcrumb( + FacetSelectors + ); + BreadboxAssertions.assertDisplayBreadcrumbClearIcon(); + }); + + describe('when deselecting a facetValue on breadcrumb', () => { + const deselectionIndex = 0; + function setupDeselectFacetValue() { + setupSelectedFacet(); + deselectBreadcrumbAtIndex(deselectionIndex); + } + + describe('verify rendering', () => { + beforeEach(() => { + setupDeselectFacetValue(); + cy.wait(TestFixture.interceptAliases.Search); + }); + BreadboxAssertions.assertDisplayBreadcrumb(false); + }); + + describe('verify analytic', () => { + beforeEach(setupDeselectFacetValue); + BreadboxAssertions.assertLogBreadcrumbFacet(field); + }); + + describe('verify selected facetValue', () => { + beforeEach(setupSelectedFacet); + BreadboxAssertions.assertDeselectCheckboxFacet( + FacetSelectors, + deselectionIndex + ); + }); + }); + }); + + describe('when select 3 values', () => { + const index = [0, 1, 2]; + function setupSelectedMultipleFacets() { + setupBreadboxWithFacet(); + index.forEach((position, i) => { + selectIdleCheckboxValueAt(FacetSelectors, position); + cy.wait(TestFixture.interceptAliases.Search); + BreadboxSelectors.breadcrumbButton().should('have.length', i + 1); + }); + } + + describe('verify rendering', () => { + beforeEach(setupSelectedMultipleFacets); + CommonAssertions.assertAccessibility(breadboxComponent); + BreadboxAssertions.assertDisplayBreadcrumb(true); + BreadboxAssertions.assertDisplayBreadcrumbClearAllButton(true); + BreadboxAssertions.assertBreadcrumbLabel(breadboxLabel); + BreadboxAssertions.assertSelectedCheckboxFacetsInBreadcrumb( + FacetSelectors + ); + BreadboxAssertions.assertDisplayBreadcrumbShowMore(false); + BreadboxAssertions.assertBreadcrumbDisplayLength(index.length); + }); + }); + + describe('when excluding 3 values', () => { + const index = [0, 1, 2]; + function setupSelectedMultipleFacets() { + setupBreadboxWithFacetWithExclusionEnabled(); + index.forEach((position, i) => { + excludeIdleCheckboxValueAt(FacetSelectors, position); + cy.wait(TestFixture.interceptAliases.Search); + BreadboxSelectors.breadcrumbButton().should('have.length', i + 1); + }); + } + + describe('verify rendering', () => { + beforeEach(setupSelectedMultipleFacets); + CommonAssertions.assertAccessibility(breadboxComponent); + BreadboxAssertions.assertDisplayBreadcrumb(true); + BreadboxAssertions.assertDisplayBreadcrumbClearAllButton(true); + BreadboxAssertions.assertBreadcrumbLabel(breadboxLabel); + BreadboxAssertions.assertExcludedCheckboxFacetsInBreadcrumb( + FacetSelectors + ); + BreadboxAssertions.assertDisplayBreadcrumbShowMore(false); + BreadboxAssertions.assertBreadcrumbDisplayLength(index.length); + }); + }); + }); + + describe('with depends-on', () => { + describe('as a dependent & parent', () => { + const facetId = 'abc'; + const parentFacetId = 'def'; + const parentField = 'filetype'; + const expectedValue = 'txt'; + beforeEach(() => { + new TestFixture() + .with( + addFacet({ + 'facet-id': facetId, + field, + [`depends-on-${parentFacetId}`]: expectedValue, + }) + ) + .with(addFacet({'facet-id': parentFacetId, field: parentField})) + .init(); + }); + + it('should control display of both parent and child properly', () => { + FacetSelectors.withId(facetId).wrapper().should('not.exist'); + FacetSelectors.withId(parentFacetId).wrapper().should('be.visible'); + }); + + it('should control the display of both parent and child properly when the dependency is met', () => { + typeFacetSearchQuery( + FacetSelectors.withId(parentFacetId), + expectedValue, + true + ); + selectIdleCheckboxValueAt(FacetSelectors.withId(parentFacetId), 0); + FacetSelectors.withId(facetId).wrapper().should('be.visible'); + FacetSelectors.withId(parentFacetId).wrapper().should('be.visible'); + }); + }); + + describe('with two dependencies', () => { + beforeEach(() => { + new TestFixture() + .with(addFacet({'facet-id': 'abc', field: 'objecttype'})) + .with(addFacet({'facet-id': 'def', field: 'filetype'})) + .with( + addFacet({ + 'facet-id': 'ghi', + field, + 'depends-on-objecttype': '', + 'depends-on-filetype': 'pdf', + }) + ) + .init(); + }); + + CommonAssertions.assertConsoleError(true); + CommonAssertions.assertContainsComponentError( + FacetSelectors.withId('ghi'), + true + ); + }); + }); + + describe('with allowed-values', () => { + beforeEach(() => { + new TestFixture() + .with( + addFacet({ + field: 'objecttype', + 'allowed-values': JSON.stringify(['FAQ', 'People']), + }) + ) + .init(); + }); + + it('returns only allowed values', () => { + FacetSelectors.values() + .should('contain.text', 'FAQ') + .should('contain.text', 'People') + .should('not.contain.text', 'Page'); + }); + }); + + describe('with custom-sort', () => { + beforeEach(() => { + new TestFixture() + .with( + addFacet({ + field: 'filetype', + 'custom-sort': JSON.stringify(['txt', 'rssitem']), + }) + ) + .init(); + }); + + it('returns values sorted in the proper order', () => { + FacetSelectors.valueLabel().eq(0).should('contain.text', 'txt'); + FacetSelectors.valueLabel().eq(1).should('contain.text', 'rssitem'); + }); + }); + + it('should use the Coveo platform defined label when none is provided', () => { + new TestFixture().with(addFacet({field: 'objecttype'})).init(); + // The label Object type is defined in the Coveo administration tool for the organization searchuisamples + FacetSelectors.labelButton().should('contain.text', 'Object type'); + }); + + it('should use No label when none is provided and there is no fallback configured in the Coveo platform', () => { + new TestFixture().with(addFacet({field: 'filetype'})).init(); + FacetSelectors.labelButton().should('contain.text', 'No label'); + }); +}); From 600ee803edd1ae2e87c5aa75524fbd0bab96ca96 Mon Sep 17 00:00:00 2001 From: Felix Perron-Brault Date: Wed, 31 Jul 2024 18:53:09 -0400 Subject: [PATCH 10/23] feat(atomic): add tab-manager support for facet components (#4210) This PR allows facets to be disabled/enabled based on the currently active tab in the tab manager. https://coveord.atlassian.net/browse/CDX-1555 --------- Co-authored-by: GitHub Actions Bot <> --- .../src/lib/stencil-generated/components.ts | 32 ++--- packages/atomic/src/components.d.ts | 128 ++++++++++++++++++ .../facets/facet-tabs/facet-tabs-utils.ts | 41 ++++++ .../atomic-category-facet.tsx | 49 +++++++ .../atomic-color-facet/atomic-color-facet.tsx | 51 ++++++- .../facets/atomic-facet/atomic-facet.tsx | 50 +++++++ .../atomic-numeric-facet.tsx | 58 +++++++- .../atomic-rating-facet.tsx | 54 +++++++- .../atomic-rating-range-facet.tsx | 54 +++++++- .../atomic-segmented-facet.tsx | 56 +++++++- .../atomic-timeframe-facet.tsx | 54 +++++++- packages/atomic/src/utils/tab-utils.spec.ts | 76 +++++++++++ packages/atomic/src/utils/tab-utils.ts | 24 ++++ 13 files changed, 704 insertions(+), 23 deletions(-) create mode 100644 packages/atomic/src/components/common/facets/facet-tabs/facet-tabs-utils.ts create mode 100644 packages/atomic/src/utils/tab-utils.spec.ts create mode 100644 packages/atomic/src/utils/tab-utils.ts diff --git a/packages/atomic-angular/projects/atomic-angular/src/lib/stencil-generated/components.ts b/packages/atomic-angular/projects/atomic-angular/src/lib/stencil-generated/components.ts index f37c37345d2..7b01808e15b 100644 --- a/packages/atomic-angular/projects/atomic-angular/src/lib/stencil-generated/components.ts +++ b/packages/atomic-angular/projects/atomic-angular/src/lib/stencil-generated/components.ts @@ -95,14 +95,14 @@ export declare interface AtomicBreadbox extends Components.AtomicBreadbox {} @ProxyCmp({ - inputs: ['basePath', 'delimitingCharacter', 'dependsOn', 'facetId', 'field', 'filterByBasePath', 'filterFacetCount', 'headingLevel', 'injectionDepth', 'isCollapsed', 'label', 'numberOfValues', 'sortCriteria', 'withSearch'] + inputs: ['basePath', 'delimitingCharacter', 'dependsOn', 'facetId', 'field', 'filterByBasePath', 'filterFacetCount', 'headingLevel', 'injectionDepth', 'isCollapsed', 'label', 'numberOfValues', 'sortCriteria', 'tabsExcluded', 'tabsIncluded', 'withSearch'] }) @Component({ selector: 'atomic-category-facet', changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['basePath', 'delimitingCharacter', 'dependsOn', 'facetId', 'field', 'filterByBasePath', 'filterFacetCount', 'headingLevel', 'injectionDepth', 'isCollapsed', 'label', 'numberOfValues', 'sortCriteria', 'withSearch'], + inputs: ['basePath', 'delimitingCharacter', 'dependsOn', 'facetId', 'field', 'filterByBasePath', 'filterFacetCount', 'headingLevel', 'injectionDepth', 'isCollapsed', 'label', 'numberOfValues', 'sortCriteria', 'tabsExcluded', 'tabsIncluded', 'withSearch'], }) export class AtomicCategoryFacet { protected el: HTMLElement; @@ -117,14 +117,14 @@ export declare interface AtomicCategoryFacet extends Components.AtomicCategoryFa @ProxyCmp({ - inputs: ['allowedValues', 'customSort', 'dependsOn', 'displayValuesAs', 'facetId', 'field', 'filterFacetCount', 'headingLevel', 'injectionDepth', 'isCollapsed', 'label', 'numberOfValues', 'resultsMustMatch', 'sortCriteria', 'withSearch'] + inputs: ['allowedValues', 'customSort', 'dependsOn', 'displayValuesAs', 'facetId', 'field', 'filterFacetCount', 'headingLevel', 'injectionDepth', 'isCollapsed', 'label', 'numberOfValues', 'resultsMustMatch', 'sortCriteria', 'tabsExcluded', 'tabsIncluded', 'withSearch'] }) @Component({ selector: 'atomic-color-facet', changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['allowedValues', 'customSort', 'dependsOn', 'displayValuesAs', 'facetId', 'field', 'filterFacetCount', 'headingLevel', 'injectionDepth', 'isCollapsed', 'label', 'numberOfValues', 'resultsMustMatch', 'sortCriteria', 'withSearch'], + inputs: ['allowedValues', 'customSort', 'dependsOn', 'displayValuesAs', 'facetId', 'field', 'filterFacetCount', 'headingLevel', 'injectionDepth', 'isCollapsed', 'label', 'numberOfValues', 'resultsMustMatch', 'sortCriteria', 'tabsExcluded', 'tabsIncluded', 'withSearch'], }) export class AtomicColorFacet { protected el: HTMLElement; @@ -495,14 +495,14 @@ export declare interface AtomicExternal extends Components.AtomicExternal {} @ProxyCmp({ - inputs: ['allowedValues', 'customSort', 'dependsOn', 'displayValuesAs', 'enableExclusion', 'facetId', 'field', 'filterFacetCount', 'headingLevel', 'injectionDepth', 'isCollapsed', 'label', 'numberOfValues', 'resultsMustMatch', 'sortCriteria', 'withSearch'] + inputs: ['allowedValues', 'customSort', 'dependsOn', 'displayValuesAs', 'enableExclusion', 'facetId', 'field', 'filterFacetCount', 'headingLevel', 'injectionDepth', 'isCollapsed', 'label', 'numberOfValues', 'resultsMustMatch', 'sortCriteria', 'tabsExcluded', 'tabsIncluded', 'withSearch'] }) @Component({ selector: 'atomic-facet', changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['allowedValues', 'customSort', 'dependsOn', 'displayValuesAs', 'enableExclusion', 'facetId', 'field', 'filterFacetCount', 'headingLevel', 'injectionDepth', 'isCollapsed', 'label', 'numberOfValues', 'resultsMustMatch', 'sortCriteria', 'withSearch'], + inputs: ['allowedValues', 'customSort', 'dependsOn', 'displayValuesAs', 'enableExclusion', 'facetId', 'field', 'filterFacetCount', 'headingLevel', 'injectionDepth', 'isCollapsed', 'label', 'numberOfValues', 'resultsMustMatch', 'sortCriteria', 'tabsExcluded', 'tabsIncluded', 'withSearch'], }) export class AtomicFacet { protected el: HTMLElement; @@ -846,14 +846,14 @@ export declare interface AtomicNotifications extends Components.AtomicNotificati @ProxyCmp({ - inputs: ['dependsOn', 'displayValuesAs', 'facetId', 'field', 'filterFacetCount', 'headingLevel', 'injectionDepth', 'isCollapsed', 'label', 'numberOfValues', 'rangeAlgorithm', 'sortCriteria', 'withInput'] + inputs: ['dependsOn', 'displayValuesAs', 'facetId', 'field', 'filterFacetCount', 'headingLevel', 'injectionDepth', 'isCollapsed', 'label', 'numberOfValues', 'rangeAlgorithm', 'sortCriteria', 'tabsExcluded', 'tabsIncluded', 'withInput'] }) @Component({ selector: 'atomic-numeric-facet', changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['dependsOn', 'displayValuesAs', 'facetId', 'field', 'filterFacetCount', 'headingLevel', 'injectionDepth', 'isCollapsed', 'label', 'numberOfValues', 'rangeAlgorithm', 'sortCriteria', 'withInput'], + inputs: ['dependsOn', 'displayValuesAs', 'facetId', 'field', 'filterFacetCount', 'headingLevel', 'injectionDepth', 'isCollapsed', 'label', 'numberOfValues', 'rangeAlgorithm', 'sortCriteria', 'tabsExcluded', 'tabsIncluded', 'withInput'], }) export class AtomicNumericFacet { protected el: HTMLElement; @@ -1053,14 +1053,14 @@ export declare interface AtomicQuickviewModal extends Components.AtomicQuickview @ProxyCmp({ - inputs: ['dependsOn', 'displayValuesAs', 'facetId', 'field', 'filterFacetCount', 'headingLevel', 'icon', 'injectionDepth', 'isCollapsed', 'label', 'maxValueInIndex', 'minValueInIndex', 'numberOfIntervals'] + inputs: ['dependsOn', 'displayValuesAs', 'facetId', 'field', 'filterFacetCount', 'headingLevel', 'icon', 'injectionDepth', 'isCollapsed', 'label', 'maxValueInIndex', 'minValueInIndex', 'numberOfIntervals', 'tabsExcluded', 'tabsIncluded'] }) @Component({ selector: 'atomic-rating-facet', changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['dependsOn', 'displayValuesAs', 'facetId', 'field', 'filterFacetCount', 'headingLevel', 'icon', 'injectionDepth', 'isCollapsed', 'label', 'maxValueInIndex', 'minValueInIndex', 'numberOfIntervals'], + inputs: ['dependsOn', 'displayValuesAs', 'facetId', 'field', 'filterFacetCount', 'headingLevel', 'icon', 'injectionDepth', 'isCollapsed', 'label', 'maxValueInIndex', 'minValueInIndex', 'numberOfIntervals', 'tabsExcluded', 'tabsIncluded'], }) export class AtomicRatingFacet { protected el: HTMLElement; @@ -1075,14 +1075,14 @@ export declare interface AtomicRatingFacet extends Components.AtomicRatingFacet @ProxyCmp({ - inputs: ['dependsOn', 'facetId', 'field', 'filterFacetCount', 'headingLevel', 'icon', 'injectionDepth', 'isCollapsed', 'label', 'maxValueInIndex', 'minValueInIndex', 'numberOfIntervals'] + inputs: ['dependsOn', 'facetId', 'field', 'filterFacetCount', 'headingLevel', 'icon', 'injectionDepth', 'isCollapsed', 'label', 'maxValueInIndex', 'minValueInIndex', 'numberOfIntervals', 'tabsExcluded', 'tabsIncluded'] }) @Component({ selector: 'atomic-rating-range-facet', changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['dependsOn', 'facetId', 'field', 'filterFacetCount', 'headingLevel', 'icon', 'injectionDepth', 'isCollapsed', 'label', 'maxValueInIndex', 'minValueInIndex', 'numberOfIntervals'], + inputs: ['dependsOn', 'facetId', 'field', 'filterFacetCount', 'headingLevel', 'icon', 'injectionDepth', 'isCollapsed', 'label', 'maxValueInIndex', 'minValueInIndex', 'numberOfIntervals', 'tabsExcluded', 'tabsIncluded'], }) export class AtomicRatingRangeFacet { protected el: HTMLElement; @@ -2068,14 +2068,14 @@ export declare interface AtomicSearchLayout extends Components.AtomicSearchLayou @ProxyCmp({ - inputs: ['allowedValues', 'customSort', 'dependsOn', 'facetId', 'field', 'filterFacetCount', 'injectionDepth', 'label', 'numberOfValues', 'sortCriteria'] + inputs: ['allowedValues', 'customSort', 'dependsOn', 'facetId', 'field', 'filterFacetCount', 'injectionDepth', 'label', 'numberOfValues', 'sortCriteria', 'tabsExcluded', 'tabsIncluded'] }) @Component({ selector: 'atomic-segmented-facet', changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['allowedValues', 'customSort', 'dependsOn', 'facetId', 'field', 'filterFacetCount', 'injectionDepth', 'label', 'numberOfValues', 'sortCriteria'], + inputs: ['allowedValues', 'customSort', 'dependsOn', 'facetId', 'field', 'filterFacetCount', 'injectionDepth', 'label', 'numberOfValues', 'sortCriteria', 'tabsExcluded', 'tabsIncluded'], }) export class AtomicSegmentedFacet { protected el: HTMLElement; @@ -2290,14 +2290,14 @@ export declare interface AtomicTimeframe extends Components.AtomicTimeframe {} @ProxyCmp({ - inputs: ['dependsOn', 'facetId', 'field', 'filterFacetCount', 'headingLevel', 'injectionDepth', 'isCollapsed', 'label', 'max', 'min', 'sortCriteria', 'withDatePicker'] + inputs: ['dependsOn', 'facetId', 'field', 'filterFacetCount', 'headingLevel', 'injectionDepth', 'isCollapsed', 'label', 'max', 'min', 'sortCriteria', 'tabsExcluded', 'tabsIncluded', 'withDatePicker'] }) @Component({ selector: 'atomic-timeframe-facet', changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['dependsOn', 'facetId', 'field', 'filterFacetCount', 'headingLevel', 'injectionDepth', 'isCollapsed', 'label', 'max', 'min', 'sortCriteria', 'withDatePicker'], + inputs: ['dependsOn', 'facetId', 'field', 'filterFacetCount', 'headingLevel', 'injectionDepth', 'isCollapsed', 'label', 'max', 'min', 'sortCriteria', 'tabsExcluded', 'tabsIncluded', 'withDatePicker'], }) export class AtomicTimeframeFacet { protected el: HTMLElement; diff --git a/packages/atomic/src/components.d.ts b/packages/atomic/src/components.d.ts index 0ed0ff8f52d..ff0c7ef5c5d 100644 --- a/packages/atomic/src/components.d.ts +++ b/packages/atomic/src/components.d.ts @@ -176,6 +176,14 @@ export namespace Components { * The sort criterion to apply to the returned facet values. Possible values are 'alphanumeric' and 'occurrences'. For this criterion to apply to the top-layer facet values, disable [facet value ordering](https://docs.coveo.com/en/l1qf4156/#facet-value-ordering) in your Dynamic Navigation Experience configuration. */ "sortCriteria": CategoryFacetSortCriterion; + /** + * The tabs on which this facet must not be displayed. This property should not be used at the same time as `tabs-included`. Set this property as a stringified JSON array, e.g., ```html ``` If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet won't be displayed on any of the specified tabs. + */ + "tabsExcluded": string[] | string; + /** + * The tabs on which the facet can be displayed. This property should not be used at the same time as `tabs-excluded`. Set this property as a stringified JSON array, e.g., ```html ``` If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet can only be displayed on the specified tabs. + */ + "tabsIncluded": string[] | string; /** * Whether this facet should contain a search box. */ @@ -260,6 +268,14 @@ export namespace Components { * The sort criterion to apply to the returned facet values. Possible values are 'score', 'alphanumeric', 'occurrences', and 'automatic'. */ "sortCriteria": FacetSortCriterion; + /** + * The tabs on which this facet must not be displayed. This property should not be used at the same time as `tabs-included`. Set this property as a stringified JSON array, e.g., ```html ``` If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet won't be displayed on any of the specified tabs. + */ + "tabsExcluded": string[] | string; + /** + * The tabs on which the facet can be displayed. This property should not be used at the same time as `tabs-excluded`. Set this property as a stringified JSON array, e.g., ```html ``` If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet can only be displayed on the specified tabs. + */ + "tabsIncluded": string[] | string; /** * Whether this facet should contain a search box. */ @@ -854,6 +870,14 @@ export namespace Components { * The sort criterion to apply to the returned facet values. Possible values are 'score', 'alphanumeric', 'alphanumericDescending', 'occurrences', and 'automatic'. */ "sortCriteria": FacetSortCriterion; + /** + * The tabs on which this facet must not be displayed. This property should not be used at the same time as `tabs-included`. Set this property as a stringified JSON array, e.g., ```html ``` If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet won't be displayed on any of the specified tabs. + */ + "tabsExcluded": string[] | string; + /** + * The tabs on which the facet can be displayed. This property should not be used at the same time as `tabs-excluded`. Set this property as a stringified JSON array, e.g., ```html ``` If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet can only be displayed on the specified tabs. + */ + "tabsIncluded": string[] | string; /** * Whether this facet should contain a search box. */ @@ -1805,6 +1829,14 @@ export namespace Components { * The sort criterion to apply to the returned facet values. Possible values are 'ascending' and 'descending'. */ "sortCriteria": RangeFacetSortCriterion; + /** + * The tabs on which this facet must not be displayed. This property should not be used at the same time as `tabs-included`. Set this property as a stringified JSON array, e.g., ```html ``` If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet won't be displayed on any of the specified tabs. + */ + "tabsExcluded": string[] | string; + /** + * The tabs on which the facet can be displayed. This property should not be used at the same time as `tabs-excluded`. Set this property as a stringified JSON array, e.g., ```html ``` If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet can only be displayed on the specified tabs. + */ + "tabsIncluded": string[] | string; /** * Whether this facet should contain an input allowing users to set custom ranges. Depending on the field, the input can allow either decimal or integer values. */ @@ -2158,6 +2190,14 @@ export namespace Components { * The number of options to display in the facet. If `maxValueInIndex` isn't specified, it will be assumed that this is also the maximum number of rating icons. */ "numberOfIntervals": number; + /** + * The tabs on which this facet must not be displayed. This property should not be used at the same time as `tabs-included`. Set this property as a stringified JSON array, e.g., ```html ``` If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet won't be displayed on any of the specified tabs. + */ + "tabsExcluded": string[] | string; + /** + * The tabs on which the facet can be displayed. This property should not be used at the same time as `tabs-excluded`. Set this property as a stringified JSON array, e.g., ```html ``` If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet can only be displayed on the specified tabs. + */ + "tabsIncluded": string[] | string; } /** * A facet is a list of values for a certain field occurring in the results, ordered using a configurable criteria (e.g., number of occurrences). @@ -2213,6 +2253,14 @@ export namespace Components { * The number of options to display in the facet. If `maxValueInIndex` isn't specified, it will be assumed that this is also the maximum number of rating icons. */ "numberOfIntervals": number; + /** + * The tabs on which this facet must not be displayed. This property should not be used at the same time as `tabs-included`. Set this property as a stringified JSON array, e.g., ```html ``` If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet won't be displayed on any of the specified tabs. + */ + "tabsExcluded": string[] | string; + /** + * The tabs on which the facet can be displayed. This property should not be used at the same time as `tabs-excluded`. Set this property as a stringified JSON array, e.g., ```html ``` If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet can only be displayed on the specified tabs. + */ + "tabsIncluded": string[] | string; } /** * The `atomic-recs-error` component handles fatal errors when performing a recommendations request on the index or Search API. When the error is known, it displays a link to relevant documentation link for debugging purposes. When the error is unknown, it displays a small text area with the JSON content of the error. @@ -3162,6 +3210,14 @@ export namespace Components { * The sort criterion to apply to the returned facet values. Possible values are 'score', 'alphanumeric', 'occurrences', and 'automatic'. */ "sortCriteria": FacetSortCriterion; + /** + * The tabs on which this facet must not be displayed. This property should not be used at the same time as `tabs-included`. Set this property as a stringified JSON array, e.g., ```html ``` If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet won't be displayed on any of the specified tabs. + */ + "tabsExcluded": string[] | string; + /** + * The tabs on which the facet can be displayed. This property should not be used at the same time as `tabs-excluded`. Set this property as a stringified JSON array, e.g., ```html ``` If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet can only be displayed on the specified tabs. + */ + "tabsIncluded": string[] | string; } /** * The 'atomic-segmented-facet-scrollable' component wraps around one or several 'atomic-segmented-facet' to provide horizontal scrolling capabilities. @@ -3395,6 +3451,14 @@ export namespace Components { * The sort criterion to apply to the returned facet values. Possible values are 'ascending' and 'descending'. */ "sortCriteria": RangeFacetSortCriterion; + /** + * The tabs on which this facet must not be displayed. This property should not be used at the same time as `tabs-included`. Set this property as a stringified JSON array, e.g., ```html ``` If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet won't be displayed on any of the specified tabs. + */ + "tabsExcluded": string[] | string; + /** + * The tabs on which the facet can be displayed. This property should not be used at the same time as `tabs-excluded`. Set this property as a stringified JSON array, e.g., ```html ``` If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet can only be displayed on the specified tabs. + */ + "tabsIncluded": string[] | string; /** * Whether this facet should contain an datepicker allowing users to set custom ranges. */ @@ -5776,6 +5840,14 @@ declare namespace LocalJSX { * The sort criterion to apply to the returned facet values. Possible values are 'alphanumeric' and 'occurrences'. For this criterion to apply to the top-layer facet values, disable [facet value ordering](https://docs.coveo.com/en/l1qf4156/#facet-value-ordering) in your Dynamic Navigation Experience configuration. */ "sortCriteria"?: CategoryFacetSortCriterion; + /** + * The tabs on which this facet must not be displayed. This property should not be used at the same time as `tabs-included`. Set this property as a stringified JSON array, e.g., ```html ``` If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet won't be displayed on any of the specified tabs. + */ + "tabsExcluded"?: string[] | string; + /** + * The tabs on which the facet can be displayed. This property should not be used at the same time as `tabs-excluded`. Set this property as a stringified JSON array, e.g., ```html ``` If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet can only be displayed on the specified tabs. + */ + "tabsIncluded"?: string[] | string; /** * Whether this facet should contain a search box. */ @@ -5860,6 +5932,14 @@ declare namespace LocalJSX { * The sort criterion to apply to the returned facet values. Possible values are 'score', 'alphanumeric', 'occurrences', and 'automatic'. */ "sortCriteria"?: FacetSortCriterion; + /** + * The tabs on which this facet must not be displayed. This property should not be used at the same time as `tabs-included`. Set this property as a stringified JSON array, e.g., ```html ``` If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet won't be displayed on any of the specified tabs. + */ + "tabsExcluded"?: string[] | string; + /** + * The tabs on which the facet can be displayed. This property should not be used at the same time as `tabs-excluded`. Set this property as a stringified JSON array, e.g., ```html ``` If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet can only be displayed on the specified tabs. + */ + "tabsIncluded"?: string[] | string; /** * Whether this facet should contain a search box. */ @@ -6416,6 +6496,14 @@ declare namespace LocalJSX { * The sort criterion to apply to the returned facet values. Possible values are 'score', 'alphanumeric', 'alphanumericDescending', 'occurrences', and 'automatic'. */ "sortCriteria"?: FacetSortCriterion; + /** + * The tabs on which this facet must not be displayed. This property should not be used at the same time as `tabs-included`. Set this property as a stringified JSON array, e.g., ```html ``` If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet won't be displayed on any of the specified tabs. + */ + "tabsExcluded"?: string[] | string; + /** + * The tabs on which the facet can be displayed. This property should not be used at the same time as `tabs-excluded`. Set this property as a stringified JSON array, e.g., ```html ``` If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet can only be displayed on the specified tabs. + */ + "tabsIncluded"?: string[] | string; /** * Whether this facet should contain a search box. */ @@ -7317,6 +7405,14 @@ declare namespace LocalJSX { * The sort criterion to apply to the returned facet values. Possible values are 'ascending' and 'descending'. */ "sortCriteria"?: RangeFacetSortCriterion; + /** + * The tabs on which this facet must not be displayed. This property should not be used at the same time as `tabs-included`. Set this property as a stringified JSON array, e.g., ```html ``` If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet won't be displayed on any of the specified tabs. + */ + "tabsExcluded"?: string[] | string; + /** + * The tabs on which the facet can be displayed. This property should not be used at the same time as `tabs-excluded`. Set this property as a stringified JSON array, e.g., ```html ``` If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet can only be displayed on the specified tabs. + */ + "tabsIncluded"?: string[] | string; /** * Whether this facet should contain an input allowing users to set custom ranges. Depending on the field, the input can allow either decimal or integer values. */ @@ -7656,6 +7752,14 @@ declare namespace LocalJSX { * The number of options to display in the facet. If `maxValueInIndex` isn't specified, it will be assumed that this is also the maximum number of rating icons. */ "numberOfIntervals"?: number; + /** + * The tabs on which this facet must not be displayed. This property should not be used at the same time as `tabs-included`. Set this property as a stringified JSON array, e.g., ```html ``` If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet won't be displayed on any of the specified tabs. + */ + "tabsExcluded"?: string[] | string; + /** + * The tabs on which the facet can be displayed. This property should not be used at the same time as `tabs-excluded`. Set this property as a stringified JSON array, e.g., ```html ``` If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet can only be displayed on the specified tabs. + */ + "tabsIncluded"?: string[] | string; } /** * A facet is a list of values for a certain field occurring in the results, ordered using a configurable criteria (e.g., number of occurrences). @@ -7711,6 +7815,14 @@ declare namespace LocalJSX { * The number of options to display in the facet. If `maxValueInIndex` isn't specified, it will be assumed that this is also the maximum number of rating icons. */ "numberOfIntervals"?: number; + /** + * The tabs on which this facet must not be displayed. This property should not be used at the same time as `tabs-included`. Set this property as a stringified JSON array, e.g., ```html ``` If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet won't be displayed on any of the specified tabs. + */ + "tabsExcluded"?: string[] | string; + /** + * The tabs on which the facet can be displayed. This property should not be used at the same time as `tabs-excluded`. Set this property as a stringified JSON array, e.g., ```html ``` If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet can only be displayed on the specified tabs. + */ + "tabsIncluded"?: string[] | string; } /** * The `atomic-recs-error` component handles fatal errors when performing a recommendations request on the index or Search API. When the error is known, it displays a link to relevant documentation link for debugging purposes. When the error is unknown, it displays a small text area with the JSON content of the error. @@ -8600,6 +8712,14 @@ declare namespace LocalJSX { * The sort criterion to apply to the returned facet values. Possible values are 'score', 'alphanumeric', 'occurrences', and 'automatic'. */ "sortCriteria"?: FacetSortCriterion; + /** + * The tabs on which this facet must not be displayed. This property should not be used at the same time as `tabs-included`. Set this property as a stringified JSON array, e.g., ```html ``` If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet won't be displayed on any of the specified tabs. + */ + "tabsExcluded"?: string[] | string; + /** + * The tabs on which the facet can be displayed. This property should not be used at the same time as `tabs-excluded`. Set this property as a stringified JSON array, e.g., ```html ``` If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet can only be displayed on the specified tabs. + */ + "tabsIncluded"?: string[] | string; } /** * The 'atomic-segmented-facet-scrollable' component wraps around one or several 'atomic-segmented-facet' to provide horizontal scrolling capabilities. @@ -8846,6 +8966,14 @@ declare namespace LocalJSX { * The sort criterion to apply to the returned facet values. Possible values are 'ascending' and 'descending'. */ "sortCriteria"?: RangeFacetSortCriterion; + /** + * The tabs on which this facet must not be displayed. This property should not be used at the same time as `tabs-included`. Set this property as a stringified JSON array, e.g., ```html ``` If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet won't be displayed on any of the specified tabs. + */ + "tabsExcluded"?: string[] | string; + /** + * The tabs on which the facet can be displayed. This property should not be used at the same time as `tabs-excluded`. Set this property as a stringified JSON array, e.g., ```html ``` If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet can only be displayed on the specified tabs. + */ + "tabsIncluded"?: string[] | string; /** * Whether this facet should contain an datepicker allowing users to set custom ranges. */ diff --git a/packages/atomic/src/components/common/facets/facet-tabs/facet-tabs-utils.ts b/packages/atomic/src/components/common/facets/facet-tabs/facet-tabs-utils.ts new file mode 100644 index 00000000000..c5ff24eb242 --- /dev/null +++ b/packages/atomic/src/components/common/facets/facet-tabs/facet-tabs-utils.ts @@ -0,0 +1,41 @@ +import {CategoryFacet, DateFacet, Facet, NumericFacet} from '@coveo/headless'; +import {shouldDisplayOnCurrentTab} from '../../../../utils/tab-utils'; + +type AnyFacetType = Facet | NumericFacet | CategoryFacet | DateFacet; + +/** + * Updates the visibility of a facet based on the active tab. + * @param tabsIncluded - An array of tab names that should include the facet. + * @param tabsExcluded - An array of tab names that should exclude the facet. + * @param activeTab - The currently active tab. + * @param facet - The facet to update. + */ +export function updateFacetVisibilityForActiveTab( + tabsIncluded: string[], + tabsExcluded: string[], + activeTab: string, + facet?: AnyFacetType +): void { + if ( + !facet || + (tabsIncluded.length === 0 && tabsExcluded.length === 0) || + !activeTab + ) { + return; + } + const shouldDisplay = shouldDisplayOnCurrentTab( + tabsIncluded, + tabsExcluded, + activeTab + ); + + if (shouldDisplay && !facet.state.enabled) { + facet.enable(); + return; + } + + if (!shouldDisplay && facet.state.enabled) { + facet.disable(); + return; + } +} diff --git a/packages/atomic/src/components/search/facets/atomic-category-facet/atomic-category-facet.tsx b/packages/atomic/src/components/search/facets/atomic-category-facet/atomic-category-facet.tsx index e151f4ea814..9c7bd1c3ba8 100644 --- a/packages/atomic/src/components/search/facets/atomic-category-facet/atomic-category-facet.tsx +++ b/packages/atomic/src/components/search/facets/atomic-category-facet/atomic-category-facet.tsx @@ -12,6 +12,9 @@ import { FacetConditionsManager, FacetValueRequest, CategoryFacetValueRequest, + TabManagerState, + TabManager, + buildTabManager, } from '@coveo/headless'; import {Component, h, State, Prop, Element, Fragment} from '@stencil/core'; import { @@ -51,6 +54,7 @@ import { shouldDisplaySearchResults, } from '../../../common/facets/facet-search/facet-search-utils'; import {FacetShowMoreLess} from '../../../common/facets/facet-show-more-less/facet-show-more-less'; +import {updateFacetVisibilityForActiveTab} from '../../../common/facets/facet-tabs/facet-tabs-utils'; import {FacetValuesGroup} from '../../../common/facets/facet-values-group/facet-values-group'; import {initializePopover} from '../../../common/facets/popover/popover-type'; import {Bindings} from '../../atomic-search-interface/atomic-search-interface'; @@ -105,6 +109,7 @@ export class AtomicCategoryFacet implements InitializableComponent { private dependenciesManager?: FacetConditionsManager; private resultIndexToFocusOnShowMore = 0; public searchStatus!: SearchStatus; + public tabManager!: TabManager; @Element() private host!: HTMLElement; @BindStateToController('facet') @@ -113,6 +118,9 @@ export class AtomicCategoryFacet implements InitializableComponent { @BindStateToController('searchStatus') @State() public searchStatusState!: SearchStatusState; + @BindStateToController('tabManager') + @State() + public tabManagerState!: TabManagerState; @State() public error!: Error; /** @@ -128,6 +136,32 @@ export class AtomicCategoryFacet implements InitializableComponent { * The field whose values you want to display in the facet. */ @Prop({reflect: true}) public field!: string; + /** + * The tabs on which the facet can be displayed. This property should not be used at the same time as `tabs-excluded`. + * + * Set this property as a stringified JSON array, e.g., + * ```html + * + * ``` + * If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet can only be displayed on the specified tabs. + */ + @ArrayProp() + @Prop({reflect: true, mutable: true}) + public tabsIncluded: string[] | string = '[]'; + + /** + * The tabs on which this facet must not be displayed. This property should not be used at the same time as `tabs-included`. + * + * Set this property as a stringified JSON array, e.g., + * ```html + * + * ``` + * If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet won't be displayed on any of the specified tabs. + */ + @ArrayProp() + @Prop({reflect: true, mutable: true}) + public tabsExcluded: string[] | string = '[]'; + /** * The number of values to request for this facet. * Also determines the number of additional values to request each time more values are shown. @@ -224,7 +258,16 @@ export class AtomicCategoryFacet implements InitializableComponent { protected facetSearchAriaMessage!: string; public initialize() { + if ( + [...this.tabsIncluded].length > 0 && + [...this.tabsExcluded].length > 0 + ) { + console.warn( + 'Values for both "tabs-included" and "tabs-excluded" have been provided. This is could lead to unexpected behaviors.' + ); + } this.searchStatus = buildSearchStatus(this.bindings.engine); + this.tabManager = buildTabManager(this.bindings.engine); const options: CategoryFacetOptions = { facetId: this.facetId, field: this.field, @@ -302,6 +345,12 @@ export class AtomicCategoryFacet implements InitializableComponent { prev: unknown, propName: keyof AtomicCategoryFacet ) { + updateFacetVisibilityForActiveTab( + [...this.tabsIncluded], + [...this.tabsExcluded], + this.tabManagerState?.activeTab, + this.facet + ); if ( this.isCategoryFacetState(prev, propName) && this.isCategoryFacetState(next, propName) diff --git a/packages/atomic/src/components/search/facets/atomic-color-facet/atomic-color-facet.tsx b/packages/atomic/src/components/search/facets/atomic-color-facet/atomic-color-facet.tsx index 9134cc6ad53..588a4fd4d72 100644 --- a/packages/atomic/src/components/search/facets/atomic-color-facet/atomic-color-facet.tsx +++ b/packages/atomic/src/components/search/facets/atomic-color-facet/atomic-color-facet.tsx @@ -13,6 +13,9 @@ import { FacetResultsMustMatch, FacetValueRequest, CategoryFacetValueRequest, + buildTabManager, + TabManager, + TabManagerState, } from '@coveo/headless'; import {Component, h, State, Prop, VNode, Element} from '@stencil/core'; import { @@ -42,6 +45,7 @@ import { shouldDisplaySearchResults, } from '../../../common/facets/facet-search/facet-search-utils'; import {FacetShowMoreLess} from '../../../common/facets/facet-show-more-less/facet-show-more-less'; +import {updateFacetVisibilityForActiveTab} from '../../../common/facets/facet-tabs/facet-tabs-utils'; import {FacetValueBox} from '../../../common/facets/facet-value-box/facet-value-box'; import {FacetValueLabelHighlight} from '../../../common/facets/facet-value-label-highlight/facet-value-label-highlight'; import {FacetValuesGroup} from '../../../common/facets/facet-values-group/facet-values-group'; @@ -98,6 +102,7 @@ export class AtomicColorFacet implements InitializableComponent { private dependenciesManager?: FacetConditionsManager; private resultIndexToFocusOnShowMore = 0; public searchStatus!: SearchStatus; + public tabManager!: TabManager; @Element() private host!: HTMLElement; @BindStateToController('facet') @@ -106,6 +111,9 @@ export class AtomicColorFacet implements InitializableComponent { @BindStateToController('searchStatus') @State() public searchStatusState!: SearchStatusState; + @BindStateToController('tabManager') + @State() + public tabManagerState!: TabManagerState; @State() public error!: Error; /** @@ -121,6 +129,32 @@ export class AtomicColorFacet implements InitializableComponent { * The field whose values you want to display in the facet. */ @Prop({reflect: true}) public field!: string; + /** + * The tabs on which the facet can be displayed. This property should not be used at the same time as `tabs-excluded`. + * + * Set this property as a stringified JSON array, e.g., + * ```html + * + * ``` + * If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet can only be displayed on the specified tabs. + */ + @ArrayProp() + @Prop({reflect: true, mutable: true}) + public tabsIncluded: string[] | string = '[]'; + + /** + * The tabs on which this facet must not be displayed. This property should not be used at the same time as `tabs-included`. + * + * Set this property as a stringified JSON array, e.g., + * ```html + * + * ``` + * If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet won't be displayed on any of the specified tabs. + */ + @ArrayProp() + @Prop({reflect: true, mutable: true}) + public tabsExcluded: string[] | string = '[]'; + /** * The number of values to request for this facet. * Also determines the number of additional values to request each time more values are shown. @@ -248,8 +282,17 @@ export class AtomicColorFacet implements InitializableComponent { protected facetSearchAriaMessage!: string; public initialize() { + if ( + [...this.tabsIncluded].length > 0 && + [...this.tabsExcluded].length > 0 + ) { + console.warn( + 'Values for both "tabs-included" and "tabs-excluded" have been provided. This is could lead to unexpected behaviors.' + ); + } this.searchStatus = buildSearchStatus(this.bindings.engine); this.facet = buildFacet(this.bindings.engine, {options: this.facetOptions}); + this.tabManager = buildTabManager(this.bindings.engine); announceFacetSearchResultsWithAriaLive( this.facet, this.label, @@ -310,6 +353,12 @@ export class AtomicColorFacet implements InitializableComponent { prev: unknown, propName: keyof AtomicColorFacet ) { + updateFacetVisibilityForActiveTab( + [...this.tabsIncluded], + [...this.tabsExcluded], + this.tabManagerState?.activeTab, + this.facet + ); if (propName === 'facetState' && prev && this.withSearch) { return shouldUpdateFacetSearchComponent( (next as FacetState).facetSearch, @@ -438,7 +487,7 @@ export class AtomicColorFacet implements InitializableComponent { >
+ * ``` + * If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet can only be displayed on the specified tabs. + */ + @ArrayProp() + @Prop({reflect: true, mutable: true}) + public tabsIncluded: string[] | string = '[]'; + + /** + * The tabs on which this facet must not be displayed. This property should not be used at the same time as `tabs-included`. + * + * Set this property as a stringified JSON array, e.g., + * ```html + * + * ``` + * If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet won't be displayed on any of the specified tabs. + */ + @ArrayProp() + @Prop({reflect: true, mutable: true}) + public tabsExcluded: string[] | string = '[]'; + /** * The number of values to request for this facet. * Also determines the number of additional values to request each time more values are shown. */ @Prop({reflect: true}) public numberOfValues = 8; + /** * Whether this facet should contain a search box. * @@ -257,9 +292,18 @@ export class AtomicFacet implements InitializableComponent { protected facetSearchAriaMessage!: string; public initialize() { + if ( + [...this.tabsIncluded].length > 0 && + [...this.tabsExcluded].length > 0 + ) { + console.warn( + 'Values for both "tabs-included" and "tabs-excluded" have been provided. This is could lead to unexpected behaviors.' + ); + } this.facet = buildFacet(this.bindings.engine, {options: this.facetOptions}); this.facetId = this.facet.state.facetId; this.searchStatus = buildSearchStatus(this.bindings.engine); + this.tabManager = buildTabManager(this.bindings.engine); this.initAriaLive(); this.initConditionManager(); this.initPopover(); @@ -278,6 +322,12 @@ export class AtomicFacet implements InitializableComponent { prev: unknown, propName: keyof AtomicFacet ) { + updateFacetVisibilityForActiveTab( + [...this.tabsIncluded], + [...this.tabsExcluded], + this.tabManagerState?.activeTab, + this.facet + ); if ( this.isFacetState(prev, propName) && this.isFacetState(next, propName) diff --git a/packages/atomic/src/components/search/facets/atomic-numeric-facet/atomic-numeric-facet.tsx b/packages/atomic/src/components/search/facets/atomic-numeric-facet/atomic-numeric-facet.tsx index 4c4288835ab..4d95d50f974 100644 --- a/packages/atomic/src/components/search/facets/atomic-numeric-facet/atomic-numeric-facet.tsx +++ b/packages/atomic/src/components/search/facets/atomic-numeric-facet/atomic-numeric-facet.tsx @@ -18,6 +18,9 @@ import { SearchStatus, SearchStatusState, NumericRangeRequest, + buildTabManager, + TabManager, + TabManagerState, } from '@coveo/headless'; import {Component, Element, h, Listen, Prop, State} from '@stencil/core'; import {FocusTargetController} from '../../../../utils/accessibility-utils'; @@ -26,7 +29,7 @@ import { InitializableComponent, InitializeBindings, } from '../../../../utils/initialization-utils'; -import {MapProp} from '../../../../utils/props-utils'; +import {ArrayProp, MapProp} from '../../../../utils/props-utils'; import {randomID} from '../../../../utils/utils'; import {parseDependsOn} from '../../../common/facets/depends-on'; import {shouldDisplayInputForFacetRange} from '../../../common/facets/facet-common'; @@ -36,6 +39,7 @@ import {FacetGuard} from '../../../common/facets/facet-guard'; import {FacetHeader} from '../../../common/facets/facet-header/facet-header'; import {NumberInputType} from '../../../common/facets/facet-number-input/number-input-type'; import {FacetPlaceholder} from '../../../common/facets/facet-placeholder/facet-placeholder'; +import {updateFacetVisibilityForActiveTab} from '../../../common/facets/facet-tabs/facet-tabs-utils'; import {formatHumanReadable} from '../../../common/facets/numeric-facet/formatter'; import {NumericFacetValueLink} from '../../../common/facets/numeric-facet/value-link'; import {NumericFacetValuesContainer} from '../../../common/facets/numeric-facet/values-container'; @@ -87,6 +91,7 @@ export class AtomicNumericFacet implements InitializableComponent { public facetForInput?: NumericFacet; public filter!: NumericFilter; public searchStatus!: SearchStatus; + public tabManager!: TabManager; @Element() private host!: HTMLElement; private manualRanges: (NumericRangeRequest & {label?: string})[] = []; private formatter: NumberFormatter = defaultNumberFormatter; @@ -101,6 +106,9 @@ export class AtomicNumericFacet implements InitializableComponent { @BindStateToController('searchStatus') @State() public searchStatusState!: SearchStatusState; + @BindStateToController('tabManager') + @State() + public tabManagerState!: TabManagerState; @State() public error!: Error; @BindStateToController('facetForInput') @State() @@ -119,6 +127,32 @@ export class AtomicNumericFacet implements InitializableComponent { * The field whose values you want to display in the facet. */ @Prop({reflect: true}) public field!: string; + /** + * The tabs on which the facet can be displayed. This property should not be used at the same time as `tabs-excluded`. + * + * Set this property as a stringified JSON array, e.g., + * ```html + * + * ``` + * If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet can only be displayed on the specified tabs. + */ + @ArrayProp() + @Prop({reflect: true, mutable: true}) + public tabsIncluded: string[] | string = '[]'; + + /** + * The tabs on which this facet must not be displayed. This property should not be used at the same time as `tabs-included`. + * + * Set this property as a stringified JSON array, e.g., + * ```html + * + * ``` + * If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet won't be displayed on any of the specified tabs. + */ + @ArrayProp() + @Prop({reflect: true, mutable: true}) + public tabsExcluded: string[] | string = '[]'; + /** * The number of values to request for this facet, when there are no manual ranges. * If the number of values is 0, no ranges will be displayed. @@ -197,13 +231,23 @@ export class AtomicNumericFacet implements InitializableComponent { } public initialize() { + if ( + [...this.tabsIncluded].length > 0 && + [...this.tabsExcluded].length > 0 + ) { + console.warn( + 'Values for both "tabs-included" and "tabs-excluded" have been provided. This is could lead to unexpected behaviors.' + ); + } this.validateProps(); + this.initializeTabManager(); this.computeFacetId(); this.initializeFacetForInput(); this.initializeFacetForRange(); this.initializeFilter(); this.initializeDependenciesManager(); this.initializeSearchStatus(); + this.registerFacetToStore(); } @@ -217,6 +261,18 @@ export class AtomicNumericFacet implements InitializableComponent { private initializeSearchStatus() { this.searchStatus = buildSearchStatus(this.bindings.engine); } + private initializeTabManager() { + this.tabManager = buildTabManager(this.bindings.engine); + } + + public componentShouldUpdate(): void { + updateFacetVisibilityForActiveTab( + [...this.tabsIncluded], + [...this.tabsExcluded], + this.tabManagerState?.activeTab, + this.facetForRange + ); + } private initializeFacetForInput() { if (!this.withInput) { diff --git a/packages/atomic/src/components/search/facets/atomic-rating-facet/atomic-rating-facet.tsx b/packages/atomic/src/components/search/facets/atomic-rating-facet/atomic-rating-facet.tsx index e517f588487..b65b0d1ef83 100644 --- a/packages/atomic/src/components/search/facets/atomic-rating-facet/atomic-rating-facet.tsx +++ b/packages/atomic/src/components/search/facets/atomic-rating-facet/atomic-rating-facet.tsx @@ -14,6 +14,9 @@ import { FacetConditionsManager, FacetValueRequest, CategoryFacetValueRequest, + buildTabManager, + TabManager, + TabManagerState, } from '@coveo/headless'; import {Component, h, State, Prop, VNode, Element} from '@stencil/core'; import Star from '../../../../images/star.svg'; @@ -23,13 +26,14 @@ import { InitializableComponent, InitializeBindings, } from '../../../../utils/initialization-utils'; -import {MapProp} from '../../../../utils/props-utils'; +import {ArrayProp, MapProp} from '../../../../utils/props-utils'; import {Rating} from '../../../common/atomic-rating/atomic-rating'; import {parseDependsOn} from '../../../common/facets/depends-on'; import {FacetInfo} from '../../../common/facets/facet-common-store'; import {FacetContainer} from '../../../common/facets/facet-container/facet-container'; import {FacetHeader} from '../../../common/facets/facet-header/facet-header'; import {FacetPlaceholder} from '../../../common/facets/facet-placeholder/facet-placeholder'; +import {updateFacetVisibilityForActiveTab} from '../../../common/facets/facet-tabs/facet-tabs-utils'; import {FacetValueCheckbox} from '../../../common/facets/facet-value-checkbox/facet-value-checkbox'; import {FacetValueLink} from '../../../common/facets/facet-value-link/facet-value-link'; import {FacetValuesGroup} from '../../../common/facets/facet-values-group/facet-values-group'; @@ -71,6 +75,7 @@ export class AtomicRatingFacet implements InitializableComponent { public facet!: NumericFacet; private dependenciesManager?: FacetConditionsManager; public searchStatus!: SearchStatus; + public tabManager!: TabManager; @Element() private host!: HTMLElement; @BindStateToController('facet') @@ -79,6 +84,9 @@ export class AtomicRatingFacet implements InitializableComponent { @BindStateToController('searchStatus') @State() public searchStatusState!: SearchStatusState; + @BindStateToController('tabManager') + @State() + public tabManagerState!: TabManagerState; @State() public error!: Error; /** @@ -94,6 +102,32 @@ export class AtomicRatingFacet implements InitializableComponent { * The field whose values you want to display in the facet. */ @Prop({reflect: true}) public field!: string; + /** + * The tabs on which the facet can be displayed. This property should not be used at the same time as `tabs-excluded`. + * + * Set this property as a stringified JSON array, e.g., + * ```html + * + * ``` + * If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet can only be displayed on the specified tabs. + */ + @ArrayProp() + @Prop({reflect: true, mutable: true}) + public tabsIncluded: string[] | string = '[]'; + + /** + * The tabs on which this facet must not be displayed. This property should not be used at the same time as `tabs-included`. + * + * Set this property as a stringified JSON array, e.g., + * ```html + * + * ``` + * If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet won't be displayed on any of the specified tabs. + */ + @ArrayProp() + @Prop({reflect: true, mutable: true}) + public tabsExcluded: string[] | string = '[]'; + /** * The number of options to display in the facet. If `maxValueInIndex` isn't specified, it will be assumed that this is also the maximum number of rating icons. */ @@ -184,8 +218,17 @@ export class AtomicRatingFacet implements InitializableComponent { } public initialize() { + if ( + [...this.tabsIncluded].length > 0 && + [...this.tabsExcluded].length > 0 + ) { + console.warn( + 'Values for both "tabs-included" and "tabs-excluded" have been provided. This is could lead to unexpected behaviors.' + ); + } this.validateProps(); this.searchStatus = buildSearchStatus(this.bindings.engine); + this.tabManager = buildTabManager(this.bindings.engine); this.initializeFacet(); this.initializeDependenciesManager(); } @@ -228,6 +271,15 @@ export class AtomicRatingFacet implements InitializableComponent { this.dependenciesManager?.stopWatching(); } + public componentShouldUpdate(): void { + updateFacetVisibilityForActiveTab( + [...this.tabsIncluded], + [...this.tabsExcluded], + this.tabManagerState?.activeTab, + this.facet + ); + } + private get isHidden() { return ( this.searchStatusState.hasError || diff --git a/packages/atomic/src/components/search/facets/atomic-rating-range-facet/atomic-rating-range-facet.tsx b/packages/atomic/src/components/search/facets/atomic-rating-range-facet/atomic-rating-range-facet.tsx index c088baf6900..3033cf40052 100644 --- a/packages/atomic/src/components/search/facets/atomic-rating-range-facet/atomic-rating-range-facet.tsx +++ b/packages/atomic/src/components/search/facets/atomic-rating-range-facet/atomic-rating-range-facet.tsx @@ -13,6 +13,9 @@ import { FacetConditionsManager, FacetValueRequest, CategoryFacetValueRequest, + buildTabManager, + TabManager, + TabManagerState, } from '@coveo/headless'; import {Component, h, State, Prop, VNode, Element} from '@stencil/core'; import Star from '../../../../images/star.svg'; @@ -22,13 +25,14 @@ import { InitializableComponent, InitializeBindings, } from '../../../../utils/initialization-utils'; -import {MapProp} from '../../../../utils/props-utils'; +import {ArrayProp, MapProp} from '../../../../utils/props-utils'; import {Rating} from '../../../common/atomic-rating/atomic-rating'; import {parseDependsOn} from '../../../common/facets/depends-on'; import {FacetInfo} from '../../../common/facets/facet-common-store'; import {FacetContainer} from '../../../common/facets/facet-container/facet-container'; import {FacetHeader} from '../../../common/facets/facet-header/facet-header'; import {FacetPlaceholder} from '../../../common/facets/facet-placeholder/facet-placeholder'; +import {updateFacetVisibilityForActiveTab} from '../../../common/facets/facet-tabs/facet-tabs-utils'; import {FacetValueLink} from '../../../common/facets/facet-value-link/facet-value-link'; import {FacetValuesGroup} from '../../../common/facets/facet-values-group/facet-values-group'; import {initializePopover} from '../../../common/facets/popover/popover-type'; @@ -67,6 +71,7 @@ export class AtomicRatingRangeFacet implements InitializableComponent { public facet!: NumericFacet; private dependenciesManager?: FacetConditionsManager; public searchStatus!: SearchStatus; + public tabManager!: TabManager; @Element() private host!: HTMLElement; @BindStateToController('facet') @@ -75,6 +80,9 @@ export class AtomicRatingRangeFacet implements InitializableComponent { @BindStateToController('searchStatus') @State() public searchStatusState!: SearchStatusState; + @BindStateToController('tabManager') + @State() + public tabManagerState!: TabManagerState; @State() public error!: Error; /** @@ -90,6 +98,32 @@ export class AtomicRatingRangeFacet implements InitializableComponent { * The field whose values you want to display in the facet. */ @Prop({reflect: true}) public field!: string; + /** + * The tabs on which the facet can be displayed. This property should not be used at the same time as `tabs-excluded`. + * + * Set this property as a stringified JSON array, e.g., + * ```html + * + * ``` + * If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet can only be displayed on the specified tabs. + */ + @ArrayProp() + @Prop({reflect: true, mutable: true}) + public tabsIncluded: string[] | string = '[]'; + + /** + * The tabs on which this facet must not be displayed. This property should not be used at the same time as `tabs-included`. + * + * Set this property as a stringified JSON array, e.g., + * ```html + * + * ``` + * If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet won't be displayed on any of the specified tabs. + */ + @ArrayProp() + @Prop({reflect: true, mutable: true}) + public tabsExcluded: string[] | string = '[]'; + /** * The number of options to display in the facet. If `maxValueInIndex` isn't specified, it will be assumed that this is also the maximum number of rating icons. */ @@ -167,11 +201,29 @@ export class AtomicRatingRangeFacet implements InitializableComponent { } public initialize() { + if ( + [...this.tabsIncluded].length > 0 && + [...this.tabsExcluded].length > 0 + ) { + console.warn( + 'Values for both "tabs-included" and "tabs-excluded" have been provided. This is could lead to unexpected behaviors.' + ); + } this.searchStatus = buildSearchStatus(this.bindings.engine); + this.tabManager = buildTabManager(this.bindings.engine); this.initializeFacet(); this.initializeDependenciesManager(); } + public componentShouldUpdate(): void { + updateFacetVisibilityForActiveTab( + [...this.tabsIncluded], + [...this.tabsExcluded], + this.tabManagerState?.activeTab, + this.facet + ); + } + public disconnectedCallback() { if (this.host.isConnected) { return; diff --git a/packages/atomic/src/components/search/facets/atomic-segmented-facet/atomic-segmented-facet.tsx b/packages/atomic/src/components/search/facets/atomic-segmented-facet/atomic-segmented-facet.tsx index 35756aa9262..103c56e04a4 100644 --- a/packages/atomic/src/components/search/facets/atomic-segmented-facet/atomic-segmented-facet.tsx +++ b/packages/atomic/src/components/search/facets/atomic-segmented-facet/atomic-segmented-facet.tsx @@ -12,6 +12,9 @@ import { FacetValueRequest, SearchStatus, SearchStatusState, + buildTabManager, + TabManager, + TabManagerState, } from '@coveo/headless'; import {Component, h, Prop, State, VNode} from '@stencil/core'; import {getFieldValueCaption} from '../../../../utils/field-utils'; @@ -22,6 +25,7 @@ import { } from '../../../../utils/initialization-utils'; import {ArrayProp, MapProp} from '../../../../utils/props-utils'; import {parseDependsOn} from '../../../common/facets/depends-on'; +import {updateFacetVisibilityForActiveTab} from '../../../common/facets/facet-tabs/facet-tabs-utils'; import {FacetValuesGroup} from '../../../common/facets/facet-values-group/facet-values-group'; import {Hidden} from '../../../common/hidden'; import {Bindings} from '../../atomic-search-interface/atomic-search-interface'; @@ -44,8 +48,12 @@ import {FacetSegmentedValue} from '../facet-segmented-value/facet-segmented-valu export class AtomicSegmentedFacet implements InitializableComponent { @InitializeBindings() public bindings!: Bindings; public searchStatus!: SearchStatus; + public tabManager!: TabManager; @State() public searchStatusState!: SearchStatusState; + @BindStateToController('tabManager') + @State() + public tabManagerState!: TabManagerState; @BindStateToController('facet') @State() public facetState!: FacetState; @@ -65,6 +73,32 @@ export class AtomicSegmentedFacet implements InitializableComponent { * Used in the `atomic-breadbox` component through the bindings store. */ @Prop({reflect: true}) public label?: string; + /** + * The tabs on which the facet can be displayed. This property should not be used at the same time as `tabs-excluded`. + * + * Set this property as a stringified JSON array, e.g., + * ```html + * + * ``` + * If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet can only be displayed on the specified tabs. + */ + @ArrayProp() + @Prop({reflect: true, mutable: true}) + public tabsIncluded: string[] | string = '[]'; + + /** + * The tabs on which this facet must not be displayed. This property should not be used at the same time as `tabs-included`. + * + * Set this property as a stringified JSON array, e.g., + * ```html + * + * ``` + * If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet won't be displayed on any of the specified tabs. + */ + @ArrayProp() + @Prop({reflect: true, mutable: true}) + public tabsExcluded: string[] | string = '[]'; + /** * Whether to exclude the parents of folded results when estimating the result count for each facet value. */ @@ -157,7 +191,16 @@ export class AtomicSegmentedFacet implements InitializableComponent { private dependenciesManager!: FacetConditionsManager; public initialize() { + if ( + [...this.tabsIncluded].length > 0 && + [...this.tabsExcluded].length > 0 + ) { + console.warn( + 'Values for both "tabs-included" and "tabs-excluded" have been provided. This is could lead to unexpected behaviors.' + ); + } this.searchStatus = buildSearchStatus(this.bindings.engine); + this.tabManager = buildTabManager(this.bindings.engine); this.facet = buildFacet(this.bindings.engine, {options: this.facetOptions}); this.facetId = this.facet.state.facetId; @@ -172,6 +215,15 @@ export class AtomicSegmentedFacet implements InitializableComponent { ); } + public componentShouldUpdate(): void { + updateFacetVisibilityForActiveTab( + [...this.tabsIncluded], + [...this.tabsExcluded], + this.tabManagerState?.activeTab, + this.facet + ); + } + disconnectedCallback() { this.dependenciesManager.stopWatching(); } @@ -253,7 +305,7 @@ export class AtomicSegmentedFacet implements InitializableComponent {
); } @@ -265,7 +317,7 @@ export class AtomicSegmentedFacet implements InitializableComponent { return (
{this.renderLabel()} {this.renderValues()} diff --git a/packages/atomic/src/components/search/facets/atomic-timeframe-facet/atomic-timeframe-facet.tsx b/packages/atomic/src/components/search/facets/atomic-timeframe-facet/atomic-timeframe-facet.tsx index 9e049738495..2478c398f26 100644 --- a/packages/atomic/src/components/search/facets/atomic-timeframe-facet/atomic-timeframe-facet.tsx +++ b/packages/atomic/src/components/search/facets/atomic-timeframe-facet/atomic-timeframe-facet.tsx @@ -16,6 +16,9 @@ import { SearchStatusState, FacetValueRequest, CategoryFacetValueRequest, + buildTabManager, + TabManager, + TabManagerState, } from '@coveo/headless'; import {Component, Element, h, Listen, Prop, State} from '@stencil/core'; import {FocusTargetController} from '../../../../utils/accessibility-utils'; @@ -24,9 +27,10 @@ import { InitializableComponent, InitializeBindings, } from '../../../../utils/initialization-utils'; -import {MapProp} from '../../../../utils/props-utils'; +import {ArrayProp, MapProp} from '../../../../utils/props-utils'; import {parseDependsOn} from '../../../common/facets/depends-on'; import {FacetPlaceholder} from '../../../common/facets/facet-placeholder/facet-placeholder'; +import {updateFacetVisibilityForActiveTab} from '../../../common/facets/facet-tabs/facet-tabs-utils'; import {TimeframeFacetCommon} from '../../../common/facets/timeframe-facet-common'; import {Bindings} from '../../atomic-search-interface/atomic-search-interface'; @@ -68,6 +72,7 @@ export class AtomicTimeframeFacet implements InitializableComponent { private timeframeFacetCommon?: TimeframeFacetCommon; public filter?: DateFilter; public searchStatus!: SearchStatus; + public tabManager!: TabManager; @Element() private host!: HTMLElement; @BindStateToController('facetForDateRange') @@ -82,6 +87,9 @@ export class AtomicTimeframeFacet implements InitializableComponent { @BindStateToController('searchStatus') @State() public searchStatusState!: SearchStatusState; + @BindStateToController('tabManager') + @State() + public tabManagerState!: TabManagerState; @State() public error!: Error; /** @@ -97,6 +105,32 @@ export class AtomicTimeframeFacet implements InitializableComponent { * The field whose values you want to display in the facet. */ @Prop({reflect: true}) public field = 'date'; + /** + * The tabs on which the facet can be displayed. This property should not be used at the same time as `tabs-excluded`. + * + * Set this property as a stringified JSON array, e.g., + * ```html + * + * ``` + * If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet can only be displayed on the specified tabs. + */ + @ArrayProp() + @Prop({reflect: true, mutable: true}) + public tabsIncluded: string[] | string = '[]'; + + /** + * The tabs on which this facet must not be displayed. This property should not be used at the same time as `tabs-included`. + * + * Set this property as a stringified JSON array, e.g., + * ```html + * + * ``` + * If you don't set this property, the facet can be displayed on any tab. Otherwise, the facet won't be displayed on any of the specified tabs. + */ + @ArrayProp() + @Prop({reflect: true, mutable: true}) + public tabsExcluded: string[] | string = '[]'; + /** * Whether this facet should contain an datepicker allowing users to set custom ranges. */ @@ -181,6 +215,14 @@ export class AtomicTimeframeFacet implements InitializableComponent { } public initialize() { + if ( + [...this.tabsIncluded].length > 0 && + [...this.tabsExcluded].length > 0 + ) { + console.warn( + 'Values for both "tabs-included" and "tabs-excluded" have been provided. This is could lead to unexpected behaviors.' + ); + } this.timeframeFacetCommon = new TimeframeFacetCommon({ facetId: this.facetId, host: this.host, @@ -211,6 +253,16 @@ export class AtomicTimeframeFacet implements InitializableComponent { sortCriteria: this.sortCriteria, }); this.searchStatus = buildSearchStatus(this.bindings.engine); + this.tabManager = buildTabManager(this.bindings.engine); + } + + public componentShouldUpdate(): void { + updateFacetVisibilityForActiveTab( + [...this.tabsIncluded], + [...this.tabsExcluded], + this.tabManagerState?.activeTab, + this.facetForDateRange + ); } public disconnectedCallback() { diff --git a/packages/atomic/src/utils/tab-utils.spec.ts b/packages/atomic/src/utils/tab-utils.spec.ts new file mode 100644 index 00000000000..de64504fac9 --- /dev/null +++ b/packages/atomic/src/utils/tab-utils.spec.ts @@ -0,0 +1,76 @@ +import {shouldDisplayOnCurrentTab} from './tab-utils'; + +describe('tab-utils', () => { + let activeTab: string; + let includeTabs: string[]; + let excludeTabs: string[]; + + beforeEach(() => { + activeTab = ''; + includeTabs = []; + excludeTabs = []; + }); + + describe('shouldDisplayOnCurrentTab', () => { + describe('Given a tab is active', () => { + beforeEach(() => { + activeTab = 'tab1'; + }); + + it('returns true when active tab is included and not excluded', () => { + includeTabs = ['tab1']; + expect( + shouldDisplayOnCurrentTab(includeTabs, excludeTabs, activeTab) + ).toBe(true); + }); + + it('returns false when active tab is excluded', () => { + excludeTabs = ['tab1']; + expect( + shouldDisplayOnCurrentTab(includeTabs, excludeTabs, activeTab) + ).toBe(false); + }); + + it('returns false when active tab is both included and excluded', () => { + includeTabs = ['tab1']; + excludeTabs = ['tab1']; + expect( + shouldDisplayOnCurrentTab(includeTabs, excludeTabs, activeTab) + ).toBe(false); + }); + + it('returns false when tabs are included, no tabs are excluded, and the active tab is different', () => { + includeTabs = ['tab2', 'tab3']; + expect( + shouldDisplayOnCurrentTab(includeTabs, excludeTabs, activeTab) + ).toBe(false); + }); + + it('returns true when tabs are excluded, no tabs are included, and the active tab is different', () => { + excludeTabs = ['tab2', 'tab3']; + expect( + shouldDisplayOnCurrentTab(includeTabs, excludeTabs, activeTab) + ).toBe(true); + }); + }); + describe('Given no tab is active', () => { + test.each([ + ['not included or excluded', [], []], + ['included', ['tab1', 'tab2', 'tab3'], []], + ['excluded', [], ['tab1', 'tab2', 'tab3']], + [ + 'excluded and included', + ['tab1', 'tab2', 'tab3'], + ['tab1', 'tab2', 'tab3'], + ], + ])( + 'returns true when no tab is active and tabs are %s', + (_, includeTabs, excludeTabs) => { + expect( + shouldDisplayOnCurrentTab(includeTabs, excludeTabs, activeTab) + ).toBe(true); + } + ); + }); + }); +}); diff --git a/packages/atomic/src/utils/tab-utils.ts b/packages/atomic/src/utils/tab-utils.ts new file mode 100644 index 00000000000..21c900ac2d5 --- /dev/null +++ b/packages/atomic/src/utils/tab-utils.ts @@ -0,0 +1,24 @@ +/** + * Determines whether the component should be displayed on the current tab. + * + * @param tabsIncluded - An array of tab names that should include the facet. + * @param tabsExcluded - An array of tab names that should exclude the facet. + * @param activeTab - The name of the currently active tab. + * @returns A boolean indicating whether the component should be displayed on the current tab. + */ +export function shouldDisplayOnCurrentTab( + includeTabs: string[], + excludeTabs: string[], + activeTab: string +) { + if (!activeTab) { + return true; + } + + const isIncluded = + includeTabs.length === 0 || includeTabs.includes(activeTab); + const isNotExcluded = + excludeTabs.length === 0 || !excludeTabs.includes(activeTab); + + return isIncluded && isNotExcluded; +} From c26ce47f7f6a3c05b6f929ace8a205e39400b2b4 Mon Sep 17 00:00:00 2001 From: ylakhdar Date: Thu, 1 Aug 2024 12:43:26 -0400 Subject: [PATCH 11/23] chore(headless SSR): add commerce ssr samples (#4228) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR introduces the headless-ssr-commerce sample project, utilizing the @coveo/headless/ssr-commerce package. The project serves as a demo application, currently featuring a product listing page. Although a search feature is planned, it is not yet implemented. Additionally, I’ve created and exposed two commerce controllers: summary and product list. These controllers are included to provide content for the listing page, but they are subject to change in the near future. Here is what you will get at this stage after going to http://localhost:3000/listing ![image](https://github.com/user-attachments/assets/73aebab6-3b38-412b-ae09-acdd49bc4853) https://coveord.atlassian.net/browse/KIT-3441 --------- Co-authored-by: Alex Prudhomme <78121423+alexprudhomme@users.noreply.github.com> --- package-lock.json | 1040 +++++++---------- package.json | 2 - .../headless-sub-controller.ssr.ts | 22 + .../headless-product-listing.ssr.ts | 23 + packages/headless/src/ssr-commerce.index.ts | 14 +- .../headless-ssr-commerce/.eslintrc.json | 6 + .../samples/headless-ssr-commerce/.gitignore | 36 + .../samples/headless-ssr-commerce/README.md | 24 + .../app/_components/listing-page.tsx | 43 + .../app/_components/product-list.tsx | 32 + .../app/_components/summary.tsx | 48 + .../app/_lib/commerce-engine-config.ts | 39 + .../app/_lib/commerce-engine.ts | 14 + .../headless-ssr-commerce/app/layout.tsx | 13 + .../app/listing/page.tsx | 15 + .../headless-ssr-commerce/app/page.tsx | 14 + .../headless-ssr-commerce/app/search/page.tsx | 3 + .../headless-ssr-commerce/next.config.mjs | 4 + .../headless-ssr-commerce/package.json | 27 + .../headless-ssr-commerce/project.json | 17 + .../headless-ssr-commerce/tsconfig.json | 27 + .../headless-ssr/app-router/package.json | 8 +- .../headless-ssr/app-router/tsconfig.json | 3 +- packages/samples/headless-ssr/package.json | 8 +- .../headless-ssr/pages-router/package.json | 8 +- .../headless-ssr/pages-router/tsconfig.json | 4 +- packages/samples/headless-ssr/tsconfig.json | 11 +- 27 files changed, 866 insertions(+), 639 deletions(-) create mode 100644 packages/headless/src/controllers/commerce/core/sub-controller/headless-sub-controller.ssr.ts create mode 100644 packages/headless/src/controllers/commerce/product-listing/headless-product-listing.ssr.ts create mode 100644 packages/samples/headless-ssr-commerce/.eslintrc.json create mode 100644 packages/samples/headless-ssr-commerce/.gitignore create mode 100644 packages/samples/headless-ssr-commerce/README.md create mode 100644 packages/samples/headless-ssr-commerce/app/_components/listing-page.tsx create mode 100644 packages/samples/headless-ssr-commerce/app/_components/product-list.tsx create mode 100644 packages/samples/headless-ssr-commerce/app/_components/summary.tsx create mode 100644 packages/samples/headless-ssr-commerce/app/_lib/commerce-engine-config.ts create mode 100644 packages/samples/headless-ssr-commerce/app/_lib/commerce-engine.ts create mode 100644 packages/samples/headless-ssr-commerce/app/layout.tsx create mode 100644 packages/samples/headless-ssr-commerce/app/listing/page.tsx create mode 100644 packages/samples/headless-ssr-commerce/app/page.tsx create mode 100644 packages/samples/headless-ssr-commerce/app/search/page.tsx create mode 100644 packages/samples/headless-ssr-commerce/next.config.mjs create mode 100644 packages/samples/headless-ssr-commerce/package.json create mode 100644 packages/samples/headless-ssr-commerce/project.json create mode 100644 packages/samples/headless-ssr-commerce/tsconfig.json diff --git a/package-lock.json b/package-lock.json index 87f5282430d..ca2201ee909 100644 --- a/package-lock.json +++ b/package-lock.json @@ -83,8 +83,6 @@ "nx": "19.0.4", "patch-package": "8.0.0", "prettier": "3.3.3", - "react": "18.3.1", - "react-dom": "18.3.1", "react-syntax-highlighter": "15.5.0", "rimraf": "5.0.9", "semver": "7.6.3", @@ -5475,6 +5473,10 @@ "resolved": "packages/samples/headless-react", "link": true }, + "node_modules/@coveo/headless-ssr-commerce-samples": { + "resolved": "packages/samples/headless-ssr-commerce", + "link": true + }, "node_modules/@coveo/headless-ssr-samples-app-router": { "resolved": "packages/samples/headless-ssr/app-router", "link": true @@ -8144,6 +8146,177 @@ "tar-stream": "^2.1.4" } }, + "node_modules/@next/env": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.5.tgz", + "integrity": "sha512-/zZGkrTOsraVfYjGP8uM0p6r0BDT6xWpkjdVbcz66PJVSpwXX3yNiRycxAuDfBKGWBrZBXRuK/YVlkNgxHGwmA==" + }, + "node_modules/@next/eslint-plugin-next": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.2.5.tgz", + "integrity": "sha512-LY3btOpPh+OTIpviNojDpUdIbHW9j0JBYBjsIp8IxtDFfYFyORvw3yNq6N231FVqQA7n7lwaf7xHbVJlA1ED7g==", + "dev": true, + "dependencies": { + "glob": "10.3.10" + } + }, + "node_modules/@next/eslint-plugin-next/node_modules/glob": { + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.5.tgz", + "integrity": "sha512-/9zVxJ+K9lrzSGli1///ujyRfon/ZneeZ+v4ptpiPoOU+GKZnm8Wj8ELWU1Pm7GHltYRBklmXMTUqM/DqQ99FQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.5.tgz", + "integrity": "sha512-vXHOPCwfDe9qLDuq7U1OYM2wUY+KQ4Ex6ozwsKxp26BlJ6XXbHleOUldenM67JRyBfVjv371oneEvYd3H2gNSA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.5.tgz", + "integrity": "sha512-vlhB8wI+lj8q1ExFW8lbWutA4M2ZazQNvMWuEDqZcuJJc78iUnLdPPunBPX8rC4IgT6lIx/adB+Cwrl99MzNaA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.5.tgz", + "integrity": "sha512-NpDB9NUR2t0hXzJJwQSGu1IAOYybsfeB+LxpGsXrRIb7QOrYmidJz3shzY8cM6+rO4Aojuef0N/PEaX18pi9OA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.5.tgz", + "integrity": "sha512-8XFikMSxWleYNryWIjiCX+gU201YS+erTUidKdyOVYi5qUQo/gRxv/3N1oZFCgqpesN6FPeqGM72Zve+nReVXQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.5.tgz", + "integrity": "sha512-6QLwi7RaYiQDcRDSU/os40r5o06b5ue7Jsk5JgdRBGGp8l37RZEh9JsLSM8QF0YDsgcosSeHjglgqi25+m04IQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.5.tgz", + "integrity": "sha512-1GpG2VhbspO+aYoMOQPQiqc/tG3LzmsdBH0LhnDS3JrtDx2QmzXe0B6mSZZiN3Bq7IOMXxv1nlsjzoS1+9mzZw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.5.tgz", + "integrity": "sha512-Igh9ZlxwvCDsu6438FXlQTHlRno4gFpJzqPjSIBZooD22tKeI4fE/YMRoHVJHmrQ2P5YL1DoZ0qaOKkbeFWeMg==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.5.tgz", + "integrity": "sha512-tEQ7oinq1/CjSG9uSTerca3v4AZ+dFa+4Yu6ihaG8Ud8ddqLQgFGcnwYls13H5X5CPDPZJdYxyeMui6muOLd4g==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -30034,6 +30207,147 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint-config-next": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.2.5.tgz", + "integrity": "sha512-zogs9zlOiZ7ka+wgUnmcM0KBEDjo4Jis7kxN1jvC0N4wynQ2MIx/KBkg4mVF63J5EK4W0QMCn7xO3vNisjaAoA==", + "dev": true, + "dependencies": { + "@next/eslint-plugin-next": "14.2.5", + "@rushstack/eslint-patch": "^1.3.3", + "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || 7.0.0 - 7.2.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-import-resolver-typescript": "^3.5.2", + "eslint-plugin-import": "^2.28.1", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.5.0 || 5.0.0-canary-7118f5dd7-20230705" + }, + "peerDependencies": { + "eslint": "^7.23.0 || ^8.0.0", + "typescript": ">=3.3.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-next/node_modules/@typescript-eslint/parser": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz", + "integrity": "sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "7.2.0", + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/typescript-estree": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-next/node_modules/@typescript-eslint/scope-manager": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.2.0.tgz", + "integrity": "sha512-Qh976RbQM/fYtjx9hs4XkayYujB/aPwglw2choHmf3zBjB4qOywWSdt9+KLRdHubGcoSwBnXUH2sR3hkyaERRg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-config-next/node_modules/@typescript-eslint/types": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.2.0.tgz", + "integrity": "sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-config-next/node_modules/@typescript-eslint/typescript-estree": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.2.0.tgz", + "integrity": "sha512-cyxS5WQQCoBwSakpMrvMXuMDEbhOo9bNHHrNcEWis6XHx6KF518tkF1wBvKIn/tpq5ZpUYK7Bdklu8qY0MsFIA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-next/node_modules/@typescript-eslint/visitor-keys": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.2.0.tgz", + "integrity": "sha512-c6EIQRHhcpl6+tO8EMR+kjkkV+ugUNXOmeASA1rlzkd8EPIriavpWoiEz1HR/VLhbVIdhqnV6E7JZm00cBDx2A==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.2.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-config-next/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/eslint-config-prettier": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", @@ -41957,6 +42271,82 @@ "node": ">= 0.4.0" } }, + "node_modules/next": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/next/-/next-14.2.5.tgz", + "integrity": "sha512-0f8aRfBVL+mpzfBjYfQuLWh2WyAwtJXCRfkPF4UJ5qd2YwrHczsrSzXU4tRMV0OAxR8ZJZWPFn6uhSC56UTsLA==", + "dependencies": { + "@next/env": "14.2.5", + "@swc/helpers": "0.5.5", + "busboy": "1.6.0", + "caniuse-lite": "^1.0.30001579", + "graceful-fs": "^4.2.11", + "postcss": "8.4.31", + "styled-jsx": "5.1.1" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=18.17.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "14.2.5", + "@next/swc-darwin-x64": "14.2.5", + "@next/swc-linux-arm64-gnu": "14.2.5", + "@next/swc-linux-arm64-musl": "14.2.5", + "@next/swc-linux-x64-gnu": "14.2.5", + "@next/swc-linux-x64-musl": "14.2.5", + "@next/swc-win32-arm64-msvc": "14.2.5", + "@next/swc-win32-ia32-msvc": "14.2.5", + "@next/swc-win32-x64-msvc": "14.2.5" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.41.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/ng-packagr": { "version": "17.3.0", "resolved": "https://registry.npmjs.org/ng-packagr/-/ng-packagr-17.3.0.tgz", @@ -60652,146 +61042,6 @@ "typescript": "5.4.5" } }, - "packages/samples/atomic-next/node_modules/@next/env": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.5.tgz", - "integrity": "sha512-/zZGkrTOsraVfYjGP8uM0p6r0BDT6xWpkjdVbcz66PJVSpwXX3yNiRycxAuDfBKGWBrZBXRuK/YVlkNgxHGwmA==" - }, - "packages/samples/atomic-next/node_modules/@next/swc-darwin-arm64": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.5.tgz", - "integrity": "sha512-/9zVxJ+K9lrzSGli1///ujyRfon/ZneeZ+v4ptpiPoOU+GKZnm8Wj8ELWU1Pm7GHltYRBklmXMTUqM/DqQ99FQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "packages/samples/atomic-next/node_modules/@next/swc-darwin-x64": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.5.tgz", - "integrity": "sha512-vXHOPCwfDe9qLDuq7U1OYM2wUY+KQ4Ex6ozwsKxp26BlJ6XXbHleOUldenM67JRyBfVjv371oneEvYd3H2gNSA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "packages/samples/atomic-next/node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.5.tgz", - "integrity": "sha512-vlhB8wI+lj8q1ExFW8lbWutA4M2ZazQNvMWuEDqZcuJJc78iUnLdPPunBPX8rC4IgT6lIx/adB+Cwrl99MzNaA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "packages/samples/atomic-next/node_modules/@next/swc-linux-arm64-musl": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.5.tgz", - "integrity": "sha512-NpDB9NUR2t0hXzJJwQSGu1IAOYybsfeB+LxpGsXrRIb7QOrYmidJz3shzY8cM6+rO4Aojuef0N/PEaX18pi9OA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "packages/samples/atomic-next/node_modules/@next/swc-linux-x64-gnu": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.5.tgz", - "integrity": "sha512-8XFikMSxWleYNryWIjiCX+gU201YS+erTUidKdyOVYi5qUQo/gRxv/3N1oZFCgqpesN6FPeqGM72Zve+nReVXQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "packages/samples/atomic-next/node_modules/@next/swc-linux-x64-musl": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.5.tgz", - "integrity": "sha512-6QLwi7RaYiQDcRDSU/os40r5o06b5ue7Jsk5JgdRBGGp8l37RZEh9JsLSM8QF0YDsgcosSeHjglgqi25+m04IQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "packages/samples/atomic-next/node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.5.tgz", - "integrity": "sha512-1GpG2VhbspO+aYoMOQPQiqc/tG3LzmsdBH0LhnDS3JrtDx2QmzXe0B6mSZZiN3Bq7IOMXxv1nlsjzoS1+9mzZw==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "packages/samples/atomic-next/node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.5.tgz", - "integrity": "sha512-Igh9ZlxwvCDsu6438FXlQTHlRno4gFpJzqPjSIBZooD22tKeI4fE/YMRoHVJHmrQ2P5YL1DoZ0qaOKkbeFWeMg==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "packages/samples/atomic-next/node_modules/@next/swc-win32-x64-msvc": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.5.tgz", - "integrity": "sha512-tEQ7oinq1/CjSG9uSTerca3v4AZ+dFa+4Yu6ihaG8Ud8ddqLQgFGcnwYls13H5X5CPDPZJdYxyeMui6muOLd4g==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, "packages/samples/atomic-next/node_modules/cypress-repeat": { "version": "2.3.5", "resolved": "https://registry.npmjs.org/cypress-repeat/-/cypress-repeat-2.3.5.tgz", @@ -60836,82 +61086,6 @@ "node": ">=8" } }, - "packages/samples/atomic-next/node_modules/next": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/next/-/next-14.2.5.tgz", - "integrity": "sha512-0f8aRfBVL+mpzfBjYfQuLWh2WyAwtJXCRfkPF4UJ5qd2YwrHczsrSzXU4tRMV0OAxR8ZJZWPFn6uhSC56UTsLA==", - "dependencies": { - "@next/env": "14.2.5", - "@swc/helpers": "0.5.5", - "busboy": "1.6.0", - "caniuse-lite": "^1.0.30001579", - "graceful-fs": "^4.2.11", - "postcss": "8.4.31", - "styled-jsx": "5.1.1" - }, - "bin": { - "next": "dist/bin/next" - }, - "engines": { - "node": ">=18.17.0" - }, - "optionalDependencies": { - "@next/swc-darwin-arm64": "14.2.5", - "@next/swc-darwin-x64": "14.2.5", - "@next/swc-linux-arm64-gnu": "14.2.5", - "@next/swc-linux-arm64-musl": "14.2.5", - "@next/swc-linux-x64-gnu": "14.2.5", - "@next/swc-linux-x64-musl": "14.2.5", - "@next/swc-win32-arm64-msvc": "14.2.5", - "@next/swc-win32-ia32-msvc": "14.2.5", - "@next/swc-win32-x64-msvc": "14.2.5" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.1.0", - "@playwright/test": "^1.41.2", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "sass": "^1.3.0" - }, - "peerDependenciesMeta": { - "@opentelemetry/api": { - "optional": true - }, - "@playwright/test": { - "optional": true - }, - "sass": { - "optional": true - } - } - }, - "packages/samples/atomic-next/node_modules/postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, "packages/samples/atomic-react": { "name": "@coveo/atomic-react-samples", "version": "0.0.0", @@ -67064,13 +67238,13 @@ "@coveo/headless": "2.75.0", "@coveo/headless-react": "1.0.21", "next": "14.2.5", - "react": "18.3.1", - "react-dom": "18.3.1" + "react": "^18.2.0", + "react-dom": "^18.2.0" }, "devDependencies": { "@types/node": "20.14.12", - "@types/react": "18.3.3", - "@types/react-dom": "18.3.0", + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", "cypress": "13.13.1", "cypress-repeat": "2.3.5", "cypress-web-vitals": "4.1.2", @@ -67082,173 +67256,42 @@ "node": "^18 || ^20" } }, + "packages/samples/headless-ssr-commerce": { + "name": "@coveo/headless-ssr-commerce-samples", + "version": "0.0.0", + "dependencies": { + "@coveo/headless": "^2.74.0", + "next": "14.2.5", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/node": "20.14.12", + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", + "eslint": "8.57", + "eslint-config-next": "14.2.5", + "typescript": "5.4.5" + } + }, "packages/samples/headless-ssr/app-router": { "name": "@coveo/headless-ssr-samples-app-router", "version": "0.0.0", "dependencies": { "@coveo/headless-ssr-samples-common": "0.0.0", "next": "14.2.5", - "react": "18.3.1", - "react-dom": "18.3.1" + "react": "^18.2.0", + "react-dom": "^18.2.0" }, "devDependencies": { "@types/node": "20.14.12", - "@types/react": "18.3.3", - "@types/react-dom": "18.3.0", + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", "eslint": "8.57.0", "eslint-config-next": "14.2.5", "typescript": "5.4.5" } }, - "packages/samples/headless-ssr/node_modules/@next/env": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.5.tgz", - "integrity": "sha512-/zZGkrTOsraVfYjGP8uM0p6r0BDT6xWpkjdVbcz66PJVSpwXX3yNiRycxAuDfBKGWBrZBXRuK/YVlkNgxHGwmA==" - }, - "packages/samples/headless-ssr/node_modules/@next/eslint-plugin-next": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.2.5.tgz", - "integrity": "sha512-LY3btOpPh+OTIpviNojDpUdIbHW9j0JBYBjsIp8IxtDFfYFyORvw3yNq6N231FVqQA7n7lwaf7xHbVJlA1ED7g==", - "dev": true, - "dependencies": { - "glob": "10.3.10" - } - }, - "packages/samples/headless-ssr/node_modules/@next/swc-darwin-arm64": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.5.tgz", - "integrity": "sha512-/9zVxJ+K9lrzSGli1///ujyRfon/ZneeZ+v4ptpiPoOU+GKZnm8Wj8ELWU1Pm7GHltYRBklmXMTUqM/DqQ99FQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "packages/samples/headless-ssr/node_modules/@next/swc-darwin-x64": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.5.tgz", - "integrity": "sha512-vXHOPCwfDe9qLDuq7U1OYM2wUY+KQ4Ex6ozwsKxp26BlJ6XXbHleOUldenM67JRyBfVjv371oneEvYd3H2gNSA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "packages/samples/headless-ssr/node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.5.tgz", - "integrity": "sha512-vlhB8wI+lj8q1ExFW8lbWutA4M2ZazQNvMWuEDqZcuJJc78iUnLdPPunBPX8rC4IgT6lIx/adB+Cwrl99MzNaA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "packages/samples/headless-ssr/node_modules/@next/swc-linux-arm64-musl": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.5.tgz", - "integrity": "sha512-NpDB9NUR2t0hXzJJwQSGu1IAOYybsfeB+LxpGsXrRIb7QOrYmidJz3shzY8cM6+rO4Aojuef0N/PEaX18pi9OA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "packages/samples/headless-ssr/node_modules/@next/swc-linux-x64-gnu": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.5.tgz", - "integrity": "sha512-8XFikMSxWleYNryWIjiCX+gU201YS+erTUidKdyOVYi5qUQo/gRxv/3N1oZFCgqpesN6FPeqGM72Zve+nReVXQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "packages/samples/headless-ssr/node_modules/@next/swc-linux-x64-musl": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.5.tgz", - "integrity": "sha512-6QLwi7RaYiQDcRDSU/os40r5o06b5ue7Jsk5JgdRBGGp8l37RZEh9JsLSM8QF0YDsgcosSeHjglgqi25+m04IQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "packages/samples/headless-ssr/node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.5.tgz", - "integrity": "sha512-1GpG2VhbspO+aYoMOQPQiqc/tG3LzmsdBH0LhnDS3JrtDx2QmzXe0B6mSZZiN3Bq7IOMXxv1nlsjzoS1+9mzZw==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "packages/samples/headless-ssr/node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.5.tgz", - "integrity": "sha512-Igh9ZlxwvCDsu6438FXlQTHlRno4gFpJzqPjSIBZooD22tKeI4fE/YMRoHVJHmrQ2P5YL1DoZ0qaOKkbeFWeMg==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "packages/samples/headless-ssr/node_modules/@next/swc-win32-x64-msvc": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.5.tgz", - "integrity": "sha512-tEQ7oinq1/CjSG9uSTerca3v4AZ+dFa+4Yu6ihaG8Ud8ddqLQgFGcnwYls13H5X5CPDPZJdYxyeMui6muOLd4g==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, "packages/samples/headless-ssr/node_modules/@types/react": { "version": "18.2.21", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.21.tgz", @@ -67269,109 +67312,6 @@ "@types/react": "*" } }, - "packages/samples/headless-ssr/node_modules/@typescript-eslint/parser": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz", - "integrity": "sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "7.2.0", - "@typescript-eslint/types": "7.2.0", - "@typescript-eslint/typescript-estree": "7.2.0", - "@typescript-eslint/visitor-keys": "7.2.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "packages/samples/headless-ssr/node_modules/@typescript-eslint/scope-manager": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.2.0.tgz", - "integrity": "sha512-Qh976RbQM/fYtjx9hs4XkayYujB/aPwglw2choHmf3zBjB4qOywWSdt9+KLRdHubGcoSwBnXUH2sR3hkyaERRg==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "7.2.0", - "@typescript-eslint/visitor-keys": "7.2.0" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "packages/samples/headless-ssr/node_modules/@typescript-eslint/types": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.2.0.tgz", - "integrity": "sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA==", - "dev": true, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "packages/samples/headless-ssr/node_modules/@typescript-eslint/typescript-estree": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.2.0.tgz", - "integrity": "sha512-cyxS5WQQCoBwSakpMrvMXuMDEbhOo9bNHHrNcEWis6XHx6KF518tkF1wBvKIn/tpq5ZpUYK7Bdklu8qY0MsFIA==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "7.2.0", - "@typescript-eslint/visitor-keys": "7.2.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "packages/samples/headless-ssr/node_modules/@typescript-eslint/visitor-keys": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.2.0.tgz", - "integrity": "sha512-c6EIQRHhcpl6+tO8EMR+kjkkV+ugUNXOmeASA1rlzkd8EPIriavpWoiEz1HR/VLhbVIdhqnV6E7JZm00cBDx2A==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "7.2.0", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, "packages/samples/headless-ssr/node_modules/cypress-repeat": { "version": "2.3.5", "resolved": "https://registry.npmjs.org/cypress-repeat/-/cypress-repeat-2.3.5.tgz", @@ -67416,155 +67356,19 @@ "node": ">=8" } }, - "packages/samples/headless-ssr/node_modules/eslint-config-next": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.2.5.tgz", - "integrity": "sha512-zogs9zlOiZ7ka+wgUnmcM0KBEDjo4Jis7kxN1jvC0N4wynQ2MIx/KBkg4mVF63J5EK4W0QMCn7xO3vNisjaAoA==", - "dev": true, - "dependencies": { - "@next/eslint-plugin-next": "14.2.5", - "@rushstack/eslint-patch": "^1.3.3", - "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || 7.0.0 - 7.2.0", - "eslint-import-resolver-node": "^0.3.6", - "eslint-import-resolver-typescript": "^3.5.2", - "eslint-plugin-import": "^2.28.1", - "eslint-plugin-jsx-a11y": "^6.7.1", - "eslint-plugin-react": "^7.33.2", - "eslint-plugin-react-hooks": "^4.5.0 || 5.0.0-canary-7118f5dd7-20230705" - }, - "peerDependencies": { - "eslint": "^7.23.0 || ^8.0.0", - "typescript": ">=3.3.1" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "packages/samples/headless-ssr/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "packages/samples/headless-ssr/node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", - "dev": true, - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "packages/samples/headless-ssr/node_modules/next": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/next/-/next-14.2.5.tgz", - "integrity": "sha512-0f8aRfBVL+mpzfBjYfQuLWh2WyAwtJXCRfkPF4UJ5qd2YwrHczsrSzXU4tRMV0OAxR8ZJZWPFn6uhSC56UTsLA==", - "dependencies": { - "@next/env": "14.2.5", - "@swc/helpers": "0.5.5", - "busboy": "1.6.0", - "caniuse-lite": "^1.0.30001579", - "graceful-fs": "^4.2.11", - "postcss": "8.4.31", - "styled-jsx": "5.1.1" - }, - "bin": { - "next": "dist/bin/next" - }, - "engines": { - "node": ">=18.17.0" - }, - "optionalDependencies": { - "@next/swc-darwin-arm64": "14.2.5", - "@next/swc-darwin-x64": "14.2.5", - "@next/swc-linux-arm64-gnu": "14.2.5", - "@next/swc-linux-arm64-musl": "14.2.5", - "@next/swc-linux-x64-gnu": "14.2.5", - "@next/swc-linux-x64-musl": "14.2.5", - "@next/swc-win32-arm64-msvc": "14.2.5", - "@next/swc-win32-ia32-msvc": "14.2.5", - "@next/swc-win32-x64-msvc": "14.2.5" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.1.0", - "@playwright/test": "^1.41.2", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "sass": "^1.3.0" - }, - "peerDependenciesMeta": { - "@opentelemetry/api": { - "optional": true - }, - "@playwright/test": { - "optional": true - }, - "sass": { - "optional": true - } - } - }, - "packages/samples/headless-ssr/node_modules/postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, "packages/samples/headless-ssr/pages-router": { "name": "@coveo/headless-ssr-samples-pages-router", "version": "0.0.0", "dependencies": { "@coveo/headless-ssr-samples-common": "0.0.0", "next": "14.2.5", - "react": "18.3.1", - "react-dom": "18.3.1" + "react": "^18.2.0", + "react-dom": "^18.2.0" }, "devDependencies": { "@types/node": "20.14.12", - "@types/react": "18.3.3", - "@types/react-dom": "18.3.0", + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", "eslint": "8.57.0", "eslint-config-next": "14.2.5", "typescript": "5.4.5" diff --git a/package.json b/package.json index ddec47b8c0a..e7031a4eb17 100644 --- a/package.json +++ b/package.json @@ -81,8 +81,6 @@ "nx": "19.0.4", "patch-package": "8.0.0", "prettier": "3.3.3", - "react": "18.3.1", - "react-dom": "18.3.1", "react-syntax-highlighter": "15.5.0", "rimraf": "5.0.9", "semver": "7.6.3", diff --git a/packages/headless/src/controllers/commerce/core/sub-controller/headless-sub-controller.ssr.ts b/packages/headless/src/controllers/commerce/core/sub-controller/headless-sub-controller.ssr.ts new file mode 100644 index 00000000000..b085699e9eb --- /dev/null +++ b/packages/headless/src/controllers/commerce/core/sub-controller/headless-sub-controller.ssr.ts @@ -0,0 +1,22 @@ +import {CommerceEngine} from '../../../../app/commerce-engine/commerce-engine'; +import {ControllerDefinitionWithoutProps} from '../../../../app/ssr-engine/types/common'; +import {buildProductListing} from '../../product-listing/headless-product-listing'; +import {ProductListingSummaryState} from '../../product-listing/summary/headless-product-listing-summary'; +import {Summary} from '../summary/headless-core-summary'; + +export type {ProductListingSummaryState, Summary}; + +export interface SummaryDefinition + extends ControllerDefinitionWithoutProps< + CommerceEngine, + Summary + > {} + +/** + * @internal + */ +export function defineQuerySummary(): SummaryDefinition { + return { + build: (engine) => buildProductListing(engine).summary(), + }; +} diff --git a/packages/headless/src/controllers/commerce/product-listing/headless-product-listing.ssr.ts b/packages/headless/src/controllers/commerce/product-listing/headless-product-listing.ssr.ts new file mode 100644 index 00000000000..6808cbb4352 --- /dev/null +++ b/packages/headless/src/controllers/commerce/product-listing/headless-product-listing.ssr.ts @@ -0,0 +1,23 @@ +import {CommerceEngine} from '../../../app/commerce-engine/commerce-engine'; +import {ControllerDefinitionWithoutProps} from '../../../app/ssr-engine/types/common'; +import {ProductListing, buildProductListing} from './headless-product-listing'; + +export type {ProductListingState as ProductListState} from './headless-product-listing'; +export type ProductList = Pick; + +export interface ProductListDefinition + extends ControllerDefinitionWithoutProps {} + +/** + * Defines a `ProductListing` controller instance. + * + * @param props - The configurable `ProductListing` properties. + * @returns The `ProductListing` controller definition. + * + * @internal + * */ +export function defineProductList(): ProductListDefinition { + return { + build: (engine) => buildProductListing(engine), + }; +} diff --git a/packages/headless/src/ssr-commerce.index.ts b/packages/headless/src/ssr-commerce.index.ts index 78594b4ee68..80c5b56748d 100644 --- a/packages/headless/src/ssr-commerce.index.ts +++ b/packages/headless/src/ssr-commerce.index.ts @@ -21,8 +21,6 @@ export type { AnalyticsRuntimeEnvironment, } from './app/engine-configuration'; export type { - ControllerDefinitionWithoutProps, - ControllerDefinitionWithProps, ControllerDefinitionsMap, InferControllerFromDefinition, InferControllersMapFromDefinition, @@ -66,6 +64,18 @@ export type { } from './controllers/commerce/core/facets/numeric/headless-commerce-numeric-facet'; export type {RegularFacet} from './controllers/commerce/core/facets/regular/headless-commerce-regular-facet'; +export type { + ProductList, + ProductListState, +} from './controllers/commerce/product-listing/headless-product-listing.ssr'; +export {defineProductList} from './controllers/commerce/product-listing/headless-product-listing.ssr'; + +export type { + ProductListingSummaryState, + Summary, +} from './controllers/commerce/core/sub-controller/headless-sub-controller.ssr'; +export {defineQuerySummary} from './controllers/commerce/core/sub-controller/headless-sub-controller.ssr'; + // TODO: KIT-3391 - export other SSR commerce controllers //#endregion diff --git a/packages/samples/headless-ssr-commerce/.eslintrc.json b/packages/samples/headless-ssr-commerce/.eslintrc.json new file mode 100644 index 00000000000..dccec630f68 --- /dev/null +++ b/packages/samples/headless-ssr-commerce/.eslintrc.json @@ -0,0 +1,6 @@ +{ + "extends": ["next/core-web-vitals", "plugin:@typescript-eslint/recommended"], + "parser": "@typescript-eslint/parser", + "plugins": ["@typescript-eslint"], + "root": true +} diff --git a/packages/samples/headless-ssr-commerce/.gitignore b/packages/samples/headless-ssr-commerce/.gitignore new file mode 100644 index 00000000000..fd3dbb571a1 --- /dev/null +++ b/packages/samples/headless-ssr-commerce/.gitignore @@ -0,0 +1,36 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js +.yarn/install-state.gz + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/packages/samples/headless-ssr-commerce/README.md b/packages/samples/headless-ssr-commerce/README.md new file mode 100644 index 00000000000..a749fa88c3f --- /dev/null +++ b/packages/samples/headless-ssr-commerce/README.md @@ -0,0 +1,24 @@ +# Server side rendering examples + +- Demonstrates usage of the framework agnostic `@coveo/headless/ssr-commerce` utils for Server-Side Rendering with headless using Next.js in a commerce app. +- Although Next.js is used to demonstrate SSR usage for convenience, the utils are not specific to Next.js. + +## Getting Started + +- Run dev server + +```bash +npm run dev +``` + +- Run prod + +```bash +npm run build && npm run prod +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +- Run tests + +TODO: add tests diff --git a/packages/samples/headless-ssr-commerce/app/_components/listing-page.tsx b/packages/samples/headless-ssr-commerce/app/_components/listing-page.tsx new file mode 100644 index 00000000000..1a8a966a904 --- /dev/null +++ b/packages/samples/headless-ssr-commerce/app/_components/listing-page.tsx @@ -0,0 +1,43 @@ +'use client'; + +import {useEffect, useState} from 'react'; +import { + hydrateStaticState, + ListingHydratedState, + ListingStaticState, +} from '../_lib/commerce-engine'; +import {ProductList} from './product-list'; +import {Summary} from './summary'; + +export default function ListingPage({ + staticState, +}: { + staticState: ListingStaticState; +}) { + const [hydratedState, setHydratedState] = useState< + ListingHydratedState | undefined + >(undefined); + + useEffect(() => { + hydrateStaticState({ + searchAction: staticState.searchAction, + }).then(({engine, controllers}) => { + setHydratedState({engine, controllers}); + }); + }, [staticState]); + + return ( + <> + {/* TODO: add UI component here */} + + + + ); +} diff --git a/packages/samples/headless-ssr-commerce/app/_components/product-list.tsx b/packages/samples/headless-ssr-commerce/app/_components/product-list.tsx new file mode 100644 index 00000000000..ef5315963d3 --- /dev/null +++ b/packages/samples/headless-ssr-commerce/app/_components/product-list.tsx @@ -0,0 +1,32 @@ +import { + ProductList as ProductListingController, + ProductListState, +} from '@coveo/headless/ssr-commerce'; +import {useEffect, useState, FunctionComponent} from 'react'; + +interface ProductListProps { + staticState: ProductListState; + controller?: ProductListingController; +} + +export const ProductList: FunctionComponent = ({ + staticState, + controller, +}) => { + const [state, setState] = useState(staticState); + + useEffect( + () => controller?.subscribe(() => setState({...controller.state})), + [controller] + ); + + return ( +
    + {state.products.map((product) => ( +
  • +

    {product.ec_name}

    +
  • + ))} +
+ ); +}; diff --git a/packages/samples/headless-ssr-commerce/app/_components/summary.tsx b/packages/samples/headless-ssr-commerce/app/_components/summary.tsx new file mode 100644 index 00000000000..8be36db7fa7 --- /dev/null +++ b/packages/samples/headless-ssr-commerce/app/_components/summary.tsx @@ -0,0 +1,48 @@ +import { + ProductListingSummaryState, + Summary as SummaryController, +} from '@coveo/headless/ssr-commerce'; +import {useEffect, useState, FunctionComponent} from 'react'; +import {ListingHydratedState} from '../_lib/commerce-engine'; + +interface SummaryProps { + hydratedState?: ListingHydratedState; + staticState: ProductListingSummaryState; + controller?: SummaryController; +} + +export const Summary: FunctionComponent = ({ + staticState, + hydratedState, + controller, +}: SummaryProps) => { + const [state, setState] = useState(staticState); + + useEffect( + () => controller?.subscribe?.(() => setState({...controller.state})), + [controller] + ); + + return ( + <> +
+ Hydrated:{' '} + +
+ + Rendered page with {state.totalNumberOfProducts} results + +
+ Rendered on{' '} + + {new Date().toISOString()} + +
+ + ); +}; diff --git a/packages/samples/headless-ssr-commerce/app/_lib/commerce-engine-config.ts b/packages/samples/headless-ssr-commerce/app/_lib/commerce-engine-config.ts new file mode 100644 index 00000000000..dbfe55679b6 --- /dev/null +++ b/packages/samples/headless-ssr-commerce/app/_lib/commerce-engine-config.ts @@ -0,0 +1,39 @@ +import { + Controller, + ControllerDefinitionsMap, + CommerceEngineDefinitionOptions, + CommerceEngine, + defineProductList, + getSampleCommerceEngineConfiguration, + defineQuerySummary, +} from '@coveo/headless/ssr-commerce'; + +type CommerceEngineConfig = CommerceEngineDefinitionOptions< + ControllerDefinitionsMap +>; + +const configuration = { + ...getSampleCommerceEngineConfiguration(), + analytics: { + trackingId: 'sports-ui-samples', + enabled: false, // TODO: enable analytics + }, +}; + +export default { + configuration: { + ...configuration, + context: { + country: 'US', + currency: 'USD', + language: 'en', + view: { + url: 'https://sports.barca.group/browse/promotions/skis-boards/surfboards', + }, + }, + }, + controllers: { + summary: defineQuerySummary(), + productList: defineProductList(), + }, +} satisfies CommerceEngineConfig; diff --git a/packages/samples/headless-ssr-commerce/app/_lib/commerce-engine.ts b/packages/samples/headless-ssr-commerce/app/_lib/commerce-engine.ts new file mode 100644 index 00000000000..b1193e6bbdd --- /dev/null +++ b/packages/samples/headless-ssr-commerce/app/_lib/commerce-engine.ts @@ -0,0 +1,14 @@ +import { + defineCommerceEngine, + InferStaticState, + InferHydratedState, +} from '@coveo/headless/ssr-commerce'; +import engineConfig from './commerce-engine-config'; + +const engineDefinition = defineCommerceEngine(engineConfig); + +// TODO: only supporting Listing static state for now (KIT-3394) +export type ListingStaticState = InferStaticState; +export type ListingHydratedState = InferHydratedState; + +export const {fetchStaticState, hydrateStaticState} = engineDefinition; diff --git a/packages/samples/headless-ssr-commerce/app/layout.tsx b/packages/samples/headless-ssr-commerce/app/layout.tsx new file mode 100644 index 00000000000..a85570e84cf --- /dev/null +++ b/packages/samples/headless-ssr-commerce/app/layout.tsx @@ -0,0 +1,13 @@ +export const metadata = { + title: 'Headless SSR examples', + description: + 'Examples of using framework agnostic @coveo/headless/ssr utils and @coveo/headless-react/ssr', +}; + +export default function RootLayout({children}: {children: React.ReactNode}) { + return ( + + {children} + + ); +} diff --git a/packages/samples/headless-ssr-commerce/app/listing/page.tsx b/packages/samples/headless-ssr-commerce/app/listing/page.tsx new file mode 100644 index 00000000000..516ef72bf6b --- /dev/null +++ b/packages/samples/headless-ssr-commerce/app/listing/page.tsx @@ -0,0 +1,15 @@ +import ListingPage from '../_components/listing-page'; +import {fetchStaticState} from '../_lib/commerce-engine'; + +/** + * This file defines a List component that uses the Coveo Headless SSR commerce library to manage its state. + * + * The Listing function is the entry point for server-side rendering (SSR). + */ +export default async function Listing() { + const staticState = await fetchStaticState(); + + return ; +} + +export const dynamic = 'force-dynamic'; diff --git a/packages/samples/headless-ssr-commerce/app/page.tsx b/packages/samples/headless-ssr-commerce/app/page.tsx new file mode 100644 index 00000000000..5c25b69d2d3 --- /dev/null +++ b/packages/samples/headless-ssr-commerce/app/page.tsx @@ -0,0 +1,14 @@ +import Link from 'next/link'; + +export default function Home() { + return ( +
    +
  • + Surfboard Listing Page +
  • +
  • + Search Page +
  • +
+ ); +} diff --git a/packages/samples/headless-ssr-commerce/app/search/page.tsx b/packages/samples/headless-ssr-commerce/app/search/page.tsx new file mode 100644 index 00000000000..058db5bf727 --- /dev/null +++ b/packages/samples/headless-ssr-commerce/app/search/page.tsx @@ -0,0 +1,3 @@ +export default async function Search() { + return

Not Supported Yet

; +} diff --git a/packages/samples/headless-ssr-commerce/next.config.mjs b/packages/samples/headless-ssr-commerce/next.config.mjs new file mode 100644 index 00000000000..4678774e6d6 --- /dev/null +++ b/packages/samples/headless-ssr-commerce/next.config.mjs @@ -0,0 +1,4 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = {}; + +export default nextConfig; diff --git a/packages/samples/headless-ssr-commerce/package.json b/packages/samples/headless-ssr-commerce/package.json new file mode 100644 index 00000000000..0dd961e1379 --- /dev/null +++ b/packages/samples/headless-ssr-commerce/package.json @@ -0,0 +1,27 @@ +{ + "name": "@coveo/headless-ssr-commerce-samples", + "description": "Examples of framework agnostic @coveo/headless/ssr utils and @coveo/headless-react/ssr using Next.js app router", + "version": "0.0.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "nx build", + "prod": "next start", + "lint": "next lint", + "build:next": "next build" + }, + "dependencies": { + "@coveo/headless": "^2.74.0", + "next": "14.2.5", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/node": "20.14.12", + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", + "eslint": "8.57", + "eslint-config-next": "14.2.5", + "typescript": "5.4.5" + } +} diff --git a/packages/samples/headless-ssr-commerce/project.json b/packages/samples/headless-ssr-commerce/project.json new file mode 100644 index 00000000000..481e49aa2ed --- /dev/null +++ b/packages/samples/headless-ssr-commerce/project.json @@ -0,0 +1,17 @@ +{ + "name": "headless-ssr-commerce-samples", + "$schema": "../../../node_modules/nx/schemas/project-schema.json", + "targets": { + "cached:build": { + "executor": "nx:run-commands", + "options": { + "command": "npm run build:next", + "cwd": "packages/samples/headless-ssr-commerce" + } + }, + "build": { + "dependsOn": ["cached:build"], + "executor": "nx:noop" + } + } +} diff --git a/packages/samples/headless-ssr-commerce/tsconfig.json b/packages/samples/headless-ssr-commerce/tsconfig.json new file mode 100644 index 00000000000..a97dd95edd2 --- /dev/null +++ b/packages/samples/headless-ssr-commerce/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./*"], + "react": ["./node_modules/@types/react"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/packages/samples/headless-ssr/app-router/package.json b/packages/samples/headless-ssr/app-router/package.json index 8b4da1ca982..d50bf2cfa3f 100644 --- a/packages/samples/headless-ssr/app-router/package.json +++ b/packages/samples/headless-ssr/app-router/package.json @@ -15,13 +15,13 @@ "dependencies": { "@coveo/headless-ssr-samples-common": "0.0.0", "next": "14.2.5", - "react": "18.3.1", - "react-dom": "18.3.1" + "react": "^18.2.0", + "react-dom": "^18.2.0" }, "devDependencies": { "@types/node": "20.14.12", - "@types/react": "18.3.3", - "@types/react-dom": "18.3.0", + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", "eslint": "8.57.0", "eslint-config-next": "14.2.5", "typescript": "5.4.5" diff --git a/packages/samples/headless-ssr/app-router/tsconfig.json b/packages/samples/headless-ssr/app-router/tsconfig.json index b5f251262fd..c970cb2086c 100644 --- a/packages/samples/headless-ssr/app-router/tsconfig.json +++ b/packages/samples/headless-ssr/app-router/tsconfig.json @@ -20,7 +20,8 @@ } ], "paths": { - "@/common/*": ["../common/*"] + "@/common/*": ["../common/*"], + "react": ["../node_modules/@types/react"] } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], diff --git a/packages/samples/headless-ssr/package.json b/packages/samples/headless-ssr/package.json index 902cc96616b..40a3025f6fa 100644 --- a/packages/samples/headless-ssr/package.json +++ b/packages/samples/headless-ssr/package.json @@ -11,13 +11,13 @@ "@coveo/headless-react": "1.0.21", "@coveo/headless": "2.75.0", "next": "14.2.5", - "react": "18.3.1", - "react-dom": "18.3.1" + "react": "^18.2.0", + "react-dom": "^18.2.0" }, "devDependencies": { "@types/node": "20.14.12", - "@types/react": "18.3.3", - "@types/react-dom": "18.3.0", + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", "eslint": "8.57.0", "eslint-config-react-app": "7.0.1", "typescript": "5.4.5", diff --git a/packages/samples/headless-ssr/pages-router/package.json b/packages/samples/headless-ssr/pages-router/package.json index 098902e0d87..186a214049d 100644 --- a/packages/samples/headless-ssr/pages-router/package.json +++ b/packages/samples/headless-ssr/pages-router/package.json @@ -15,13 +15,13 @@ "dependencies": { "@coveo/headless-ssr-samples-common": "0.0.0", "next": "14.2.5", - "react": "18.3.1", - "react-dom": "18.3.1" + "react": "^18.2.0", + "react-dom": "^18.2.0" }, "devDependencies": { "@types/node": "20.14.12", - "@types/react": "18.3.3", - "@types/react-dom": "18.3.0", + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", "eslint": "8.57.0", "eslint-config-next": "14.2.5", "typescript": "5.4.5" diff --git a/packages/samples/headless-ssr/pages-router/tsconfig.json b/packages/samples/headless-ssr/pages-router/tsconfig.json index 1641607b4c8..f57179a6377 100644 --- a/packages/samples/headless-ssr/pages-router/tsconfig.json +++ b/packages/samples/headless-ssr/pages-router/tsconfig.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "target": "es5", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, @@ -14,7 +13,8 @@ "jsx": "preserve", "incremental": true, "paths": { - "@/common/*": ["../common/*"] + "@/common/*": ["../common/*"], + "react": ["../node_modules/@types/react"] } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], diff --git a/packages/samples/headless-ssr/tsconfig.json b/packages/samples/headless-ssr/tsconfig.json index 9c758e237ae..267f8fdbde6 100644 --- a/packages/samples/headless-ssr/tsconfig.json +++ b/packages/samples/headless-ssr/tsconfig.json @@ -4,7 +4,14 @@ // For Cypress https://github.com/cypress-io/cypress/issues/27448 "compilerOptions": { "module": "es2015", - "moduleResolution": "node" + "moduleResolution": "node", + "jsx": "react-jsx" } - } + }, + "compilerOptions": { + "paths": { + "react": ["./node_modules/@types/react"] + } + }, + "include": ["**/*.ts", "**/*.tsx", "**/**/*.ts", "**/**/*.tsx"] } From e8ebb095978794b625caec8aed3e985646513469 Mon Sep 17 00:00:00 2001 From: Nico Labarre Date: Thu, 1 Aug 2024 15:51:02 -0400 Subject: [PATCH 12/23] fix(commerce): fix field suggestions state update (#4245) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Field suggestion values weren't updating when the query changed. To fix this, we fetch the facet search state using a selector, just like we do for facets (thanks @fbeaudoincoveo!). I've also fixed a few other things: - Load the proper reducers in field suggestions controllers - Add the reducer to the commerce engine - Type the reducer loaders - Replace the `Subscribable` type to `Controller` on field suggestions controllers - Adjusted the field suggestions integration test to rely on controllers instead of engine state updates. This was actually a clue that the current behavior was broken, as it was not possible to await state changes on the field suggestions controllers 😅 [CAPI-1201] [CAPI-1201]: https://coveord.atlassian.net/browse/CAPI-1201?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --- .../app/commerce-engine/commerce-engine.ts | 2 + packages/headless/src/commerce.index.ts | 5 +- ...eadless-category-field-suggestions.test.ts | 11 +++++ .../headless-category-field-suggestions.ts | 48 ++++++++++++++++--- ...adless-field-suggestions-generator.test.ts | 6 --- .../headless-field-suggestions-generator.ts | 18 ++----- .../headless-field-suggestions.test.ts | 11 +++++ .../headless-field-suggestions.ts | 40 +++++++++++++--- .../src/integration-tests/commerce.test.ts | 6 +-- 9 files changed, 110 insertions(+), 37 deletions(-) diff --git a/packages/headless/src/app/commerce-engine/commerce-engine.ts b/packages/headless/src/app/commerce-engine/commerce-engine.ts index 55369eddc8f..b027271bd5a 100644 --- a/packages/headless/src/app/commerce-engine/commerce-engine.ts +++ b/packages/headless/src/app/commerce-engine/commerce-engine.ts @@ -9,6 +9,7 @@ import {setContext} from '../../features/commerce/context/context-actions'; import {contextReducer} from '../../features/commerce/context/context-slice'; import {didYouMeanReducer} from '../../features/commerce/did-you-mean/did-you-mean-slice'; import {commerceFacetSetReducer} from '../../features/commerce/facets/facet-set/facet-set-slice'; +import {fieldSuggestionsOrderReducer} from '../../features/commerce/facets/field-suggestions-order/field-suggestions-order-slice'; import {manualNumericFacetReducer} from '../../features/commerce/facets/numeric-facet/manual-numeric-facet-slice'; import {paginationReducer} from '../../features/commerce/pagination/pagination-slice'; import {productListingReducer} from '../../features/commerce/product-listing/product-listing-slice'; @@ -45,6 +46,7 @@ const commerceEngineReducers = { commercePagination: paginationReducer, commerceSort: sortReducer, facetOrder: facetOrderReducer, + fieldSuggestionsOrder: fieldSuggestionsOrderReducer, facetSearchSet: specificFacetSearchSetReducer, categoryFacetSearchSet: categoryFacetSearchSetReducer, commerceFacetSet: commerceFacetSetReducer, diff --git a/packages/headless/src/commerce.index.ts b/packages/headless/src/commerce.index.ts index 3f00217da79..ff7e9318e05 100644 --- a/packages/headless/src/commerce.index.ts +++ b/packages/headless/src/commerce.index.ts @@ -298,7 +298,10 @@ export type { CategoryFieldSuggestions, CategoryFieldSuggestionsState, } from './controllers/commerce/field-suggestions/headless-category-field-suggestions'; -export type {FieldSuggestionsGenerator} from './controllers/commerce/field-suggestions/headless-field-suggestions-generator'; +export type { + FieldSuggestionsGenerator, + GeneratedFieldSuggestionsControllers, +} from './controllers/commerce/field-suggestions/headless-field-suggestions-generator'; export type {FieldSuggestionsFacet} from './features/commerce/facets/field-suggestions-order/field-suggestions-order-state.ts'; export {buildFieldSuggestionsGenerator} from './controllers/commerce/field-suggestions/headless-field-suggestions-generator'; diff --git a/packages/headless/src/controllers/commerce/field-suggestions/headless-category-field-suggestions.test.ts b/packages/headless/src/controllers/commerce/field-suggestions/headless-category-field-suggestions.test.ts index 42d5e7e9e70..a0bbfa9d5da 100644 --- a/packages/headless/src/controllers/commerce/field-suggestions/headless-category-field-suggestions.test.ts +++ b/packages/headless/src/controllers/commerce/field-suggestions/headless-category-field-suggestions.test.ts @@ -1,5 +1,8 @@ import {executeCommerceFieldSuggest} from '../../../features/commerce/facets/facet-search-set/commerce-facet-search-actions'; +import {commerceFacetSetReducer as commerceFacetSet} from '../../../features/commerce/facets/facet-set/facet-set-slice'; import {CategoryFacetRequest} from '../../../features/commerce/facets/facet-set/interfaces/request'; +import {fieldSuggestionsOrderReducer as fieldSuggestionsOrder} from '../../../features/commerce/facets/field-suggestions-order/field-suggestions-order-slice'; +import {categoryFacetSearchSetReducer as categoryFacetSearchSet} from '../../../features/facets/facet-search-set/category/category-facet-search-set-slice'; import {updateFacetSearch} from '../../../features/facets/facet-search-set/specific/specific-facet-search-actions'; import {CommerceAppState} from '../../../state/commerce-app-state'; import {buildMockCategoryFacetSearch} from '../../../test/mock-category-facet-search'; @@ -58,6 +61,14 @@ describe('categoryFieldSuggestions', () => { initFacet(); }); + it('adds correct reducers to engine', () => { + expect(engine.addReducers).toHaveBeenCalledWith({ + fieldSuggestionsOrder, + categoryFacetSearchSet, + commerceFacetSet, + }); + }); + it('should dispatch an #updateFacetSearch and #executeFieldSuggest action on #updateText', () => { fieldSuggestions.updateText('foo'); expect(updateFacetSearch).toHaveBeenCalledWith({ diff --git a/packages/headless/src/controllers/commerce/field-suggestions/headless-category-field-suggestions.ts b/packages/headless/src/controllers/commerce/field-suggestions/headless-category-field-suggestions.ts index 52b75b7de3b..531851bccc3 100644 --- a/packages/headless/src/controllers/commerce/field-suggestions/headless-category-field-suggestions.ts +++ b/packages/headless/src/controllers/commerce/field-suggestions/headless-category-field-suggestions.ts @@ -1,11 +1,23 @@ +import {createSelector} from '@reduxjs/toolkit'; import {FieldSuggestionsFacet} from '../../../api/commerce/search/query-suggest/query-suggest-response'; -import {CommerceEngine} from '../../../app/commerce-engine/commerce-engine'; +import { + CommerceEngine, + CommerceEngineState, +} from '../../../app/commerce-engine/commerce-engine'; import {stateKey} from '../../../app/state-key'; import {commerceFacetSetReducer as commerceFacetSet} from '../../../features/commerce/facets/facet-set/facet-set-slice'; +import {fieldSuggestionsOrderReducer as fieldSuggestionsOrder} from '../../../features/commerce/facets/field-suggestions-order/field-suggestions-order-slice'; +import {categoryFacetSearchSetReducer as categoryFacetSearchSet} from '../../../features/facets/facet-search-set/category/category-facet-search-set-slice'; +import { + CategoryFacetSearchSection, + CommerceFacetSetSection, + FacetSearchSection, + FieldSuggestionsOrderSection, +} from '../../../state/state-sections'; import {loadReducerError} from '../../../utils/errors'; import { buildController, - Subscribable, + Controller, } from '../../controller/headless-controller'; import { CategoryFieldSuggestionsState as CoreCategoryFieldSuggestionsState, @@ -28,7 +40,7 @@ export type CategoryFieldSuggestionsState = CoreCategoryFieldSuggestionsState & * This controller is a wrapper around the basic category facet controller search functionality, and thus exposes similar options and properties. */ export interface CategoryFieldSuggestions - extends Subscribable, + extends Controller, FacetControllerType<'hierarchical'> { /** * Requests field suggestions based on a query. @@ -81,13 +93,26 @@ export function buildCategoryFieldSuggestions( isForFieldSuggestions: true, }); + const getState = () => engine[stateKey]; + const getFacetForFieldSuggestions = (facetId: string) => { - return engine[stateKey].fieldSuggestionsOrder!.find( + return getState().fieldSuggestionsOrder.find( (facet) => facet.facetId === facetId )!; }; const controller = buildController(engine); + + const facetSearchStateSelector = createSelector( + (state: CommerceEngineState) => + state.categoryFacetSearchSet[options.facetId], + (facetSearch) => ({ + isLoading: facetSearch.isLoading, + moreValuesAvailable: facetSearch.response.moreValuesAvailable, + query: facetSearch.options.query, + values: facetSearch.response.values, + }) + ); return { ...controller, ...facetSearch, @@ -103,7 +128,7 @@ export function buildCategoryFieldSuggestions( displayName: facet.displayName, field: facet.field, facetId: facet.facetId, - ...facetSearch.state, + ...facetSearchStateSelector(getState()), }; }, @@ -113,7 +138,16 @@ export function buildCategoryFieldSuggestions( function loadFieldSuggestionsReducers( engine: CommerceEngine -): engine is CommerceEngine { - engine.addReducers({commerceFacetSet}); +): engine is CommerceEngine< + FieldSuggestionsOrderSection & + CommerceFacetSetSection & + CategoryFacetSearchSection & + FacetSearchSection +> { + engine.addReducers({ + fieldSuggestionsOrder, + categoryFacetSearchSet, + commerceFacetSet, + }); return true; } diff --git a/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions-generator.test.ts b/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions-generator.test.ts index c0dad1dca0d..f20d4dddc17 100644 --- a/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions-generator.test.ts +++ b/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions-generator.test.ts @@ -1,9 +1,6 @@ import {FacetSearchType} from '../../../api/commerce/facet-search/facet-search-request'; import {FieldSuggestionsFacet} from '../../../api/commerce/search/query-suggest/query-suggest-response'; -import {commerceFacetSetReducer as commerceFacetSet} from '../../../features/commerce/facets/facet-set/facet-set-slice'; import {fieldSuggestionsOrderReducer as fieldSuggestionsOrder} from '../../../features/commerce/facets/field-suggestions-order/field-suggestions-order-slice'; -import {categoryFacetSearchSetReducer as categoryFacetSearchSet} from '../../../features/facets/facet-search-set/category/category-facet-search-set-slice'; -import {specificFacetSearchSetReducer as facetSearchSet} from '../../../features/facets/facet-search-set/specific/specific-facet-search-set-slice'; import {CommerceAppState} from '../../../state/commerce-app-state'; import {buildMockCategoryFacetSearch} from '../../../test/mock-category-facet-search'; import {buildMockCommerceFacetRequest} from '../../../test/mock-commerce-facet-request'; @@ -71,9 +68,6 @@ describe('fieldSuggestionsGenerator', () => { it('adds correct reducers to engine', () => { expect(engine.addReducers).toHaveBeenCalledWith({ fieldSuggestionsOrder, - commerceFacetSet, - facetSearchSet, - categoryFacetSearchSet, }); }); diff --git a/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions-generator.ts b/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions-generator.ts index 9a2a2e5bafc..92719e8fe1e 100644 --- a/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions-generator.ts +++ b/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions-generator.ts @@ -5,12 +5,10 @@ import { CommerceEngineState, } from '../../../app/commerce-engine/commerce-engine'; import {stateKey} from '../../../app/state-key'; -import {commerceFacetSetReducer as commerceFacetSet} from '../../../features/commerce/facets/facet-set/facet-set-slice'; import {fieldSuggestionsOrderReducer as fieldSuggestionsOrder} from '../../../features/commerce/facets/field-suggestions-order/field-suggestions-order-slice'; import {FieldSuggestionsFacet} from '../../../features/commerce/facets/field-suggestions-order/field-suggestions-order-state'; import {executeSearch} from '../../../features/commerce/search/search-actions'; -import {categoryFacetSearchSetReducer as categoryFacetSearchSet} from '../../../features/facets/facet-search-set/category/category-facet-search-set-slice'; -import {specificFacetSearchSetReducer as facetSearchSet} from '../../../features/facets/facet-search-set/specific/specific-facet-search-set-slice'; +import {FieldSuggestionsOrderSection} from '../../../state/state-sections'; import {loadReducerError} from '../../../utils/errors'; import { buildController, @@ -67,11 +65,8 @@ export function buildFieldSuggestionsGenerator( const controller = buildController(engine); const createFieldSuggestionsControllers = createSelector( - (state: CommerceEngineState) => state.fieldSuggestionsOrder!, - (state: CommerceEngineState) => state.commerceFacetSet, - (state: CommerceEngineState) => state.facetSearchSet, - (state: CommerceEngineState) => state.categoryFacetSearchSet, - (facetOrder, _commerceFacetSet, _facetSearchSet, _categoryFacetSearchSet) => + (state: CommerceEngineState) => state.fieldSuggestionsOrder, + (facetOrder) => facetOrder.map(({type, facetId}) => { switch (type) { case 'hierarchical': @@ -94,19 +89,16 @@ export function buildFieldSuggestionsGenerator( }, get state() { - return engine[stateKey].fieldSuggestionsOrder!; + return engine[stateKey].fieldSuggestionsOrder; }, }; } function loadFieldSuggestionsGeneratorReducers( engine: CommerceEngine -): engine is CommerceEngine { +): engine is CommerceEngine { engine.addReducers({ fieldSuggestionsOrder, - commerceFacetSet, - facetSearchSet, - categoryFacetSearchSet, }); return true; } diff --git a/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions.test.ts b/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions.test.ts index 5d22f2f65cb..be836448924 100644 --- a/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions.test.ts +++ b/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions.test.ts @@ -1,6 +1,9 @@ import {executeCommerceFieldSuggest} from '../../../features/commerce/facets/facet-search-set/commerce-facet-search-actions'; +import {commerceFacetSetReducer as commerceFacetSet} from '../../../features/commerce/facets/facet-set/facet-set-slice'; import {RegularFacetRequest} from '../../../features/commerce/facets/facet-set/interfaces/request'; +import {fieldSuggestionsOrderReducer as fieldSuggestionsOrder} from '../../../features/commerce/facets/field-suggestions-order/field-suggestions-order-slice'; import {updateFacetSearch} from '../../../features/facets/facet-search-set/specific/specific-facet-search-actions'; +import {specificFacetSearchSetReducer as facetSearchSet} from '../../../features/facets/facet-search-set/specific/specific-facet-search-set-slice'; import {CommerceAppState} from '../../../state/commerce-app-state'; import {buildMockCommerceFacetRequest} from '../../../test/mock-commerce-facet-request'; import {buildMockCommerceFacetSlice} from '../../../test/mock-commerce-facet-slice'; @@ -60,6 +63,14 @@ describe('fieldSuggestions', () => { initFacet(); }); + it('adds correct reducers to engine', () => { + expect(engine.addReducers).toHaveBeenCalledWith({ + fieldSuggestionsOrder, + commerceFacetSet, + facetSearchSet, + }); + }); + it('should dispatch an #updateFacetSearch and #executeFieldSuggest action on #updateText', () => { fieldSuggestions.updateText('foo'); expect(updateFacetSearch).toHaveBeenCalled(); diff --git a/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions.ts b/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions.ts index 548d9d0c072..6f0ed218cce 100644 --- a/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions.ts +++ b/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions.ts @@ -1,12 +1,23 @@ +import {createSelector} from '@reduxjs/toolkit'; import {FieldSuggestionsFacet} from '../../../api/commerce/search/query-suggest/query-suggest-response'; import {SpecificFacetSearchResult} from '../../../api/search/facet-search/specific-facet-search/specific-facet-search-response'; -import {CommerceEngine} from '../../../app/commerce-engine/commerce-engine'; +import { + CommerceEngine, + CommerceEngineState, +} from '../../../app/commerce-engine/commerce-engine'; import {stateKey} from '../../../app/state-key'; import {commerceFacetSetReducer as commerceFacetSet} from '../../../features/commerce/facets/facet-set/facet-set-slice'; +import {fieldSuggestionsOrderReducer as fieldSuggestionsOrder} from '../../../features/commerce/facets/field-suggestions-order/field-suggestions-order-slice'; +import {specificFacetSearchSetReducer as facetSearchSet} from '../../../features/facets/facet-search-set/specific/specific-facet-search-set-slice'; +import { + CommerceFacetSetSection, + FacetSearchSection, + FieldSuggestionsOrderSection, +} from '../../../state/state-sections'; import {loadReducerError} from '../../../utils/errors'; import { buildController, - Subscribable, + Controller, } from '../../controller/headless-controller'; import {FacetControllerType} from '../core/facets/headless-core-commerce-facet'; import {RegularFacetOptions} from '../core/facets/regular/headless-commerce-regular-facet'; @@ -28,7 +39,7 @@ export type FieldSuggestionsState = RegularFacetSearchState & * This controller is a wrapper around the basic facet controller search functionality, and thus exposes similar options and properties. */ export interface FieldSuggestions - extends Subscribable, + extends Controller, FacetControllerType<'regular'> { /** * Requests field suggestions based on a query. @@ -99,13 +110,26 @@ export function buildFieldSuggestions( isForFieldSuggestions: true, }); + const getState = () => engine[stateKey]; + const getFacetForFieldSuggestions = (facetId: string) => { - return engine[stateKey].fieldSuggestionsOrder!.find( + return getState().fieldSuggestionsOrder.find( (facet) => facet.facetId === facetId )!; }; const controller = buildController(engine); + + const facetSearchStateSelector = createSelector( + (state: CommerceEngineState) => state.facetSearchSet[options.facetId], + (facetSearch) => ({ + isLoading: facetSearch.isLoading, + moreValuesAvailable: facetSearch.response.moreValuesAvailable, + query: facetSearch.options.query, + values: facetSearch.response.values, + }) + ); + return { ...controller, ...facetSearch, @@ -121,7 +145,7 @@ export function buildFieldSuggestions( displayName: facet.displayName, field: facet.field, facetId: facet.facetId, - ...facetSearch.state, + ...facetSearchStateSelector(getState()), }; }, @@ -131,7 +155,9 @@ export function buildFieldSuggestions( function loadFieldSuggestionsReducers( engine: CommerceEngine -): engine is CommerceEngine { - engine.addReducers({commerceFacetSet}); +): engine is CommerceEngine< + FieldSuggestionsOrderSection & CommerceFacetSetSection & FacetSearchSection +> { + engine.addReducers({fieldSuggestionsOrder, commerceFacetSet, facetSearchSet}); return true; } diff --git a/packages/headless/src/integration-tests/commerce.test.ts b/packages/headless/src/integration-tests/commerce.test.ts index 5782367d9e0..9cf5b318fdf 100644 --- a/packages/headless/src/integration-tests/commerce.test.ts +++ b/packages/headless/src/integration-tests/commerce.test.ts @@ -154,9 +154,9 @@ describe.skip('commerce', () => { expect(generator.fieldSuggestions).toHaveLength(3); for (const controller of generator.fieldSuggestions) { - await waitForNextStateChange(engine, { + await waitForNextStateChange(controller, { action: () => controller.updateText('can'), - expectedSubscriberCalls: 3, + expectedSubscriberCalls: 2, }); } @@ -169,7 +169,7 @@ describe.skip('commerce', () => { await search(box, 'acc'); for (const controller of generator.fieldSuggestions) { - await waitForNextStateChange(engine, { + await waitForNextStateChange(controller, { action: () => controller.updateText('acc'), expectedSubscriberCalls: 3, }); From 99bbef1e52cde79e06b1b5f7815d2a6fba7d474b Mon Sep 17 00:00:00 2001 From: ylakhdar Date: Fri, 2 Aug 2024 15:20:54 -0400 Subject: [PATCH 13/23] feat(headless SSR): support navigator context in both Engine and fetch/hydrate functions (#4231) Introducing a method for configuring the navigator context provider. Users are alerted with a warning if the navigator context is not established on both the client and server sides. I also updated the samples to demonstrate the navigator's application within a Next.js framework, though the approach is not exclusive to this framework. There are two methods for setting the navigator context: 1. During engine configuration. 2. While initializing the app's static state and hydrating the app state, if the first method is impractical. The example in the SSR samples highlights a scenario where, due to the framework's limitations, setting the navigator context directly within the engine configuration is not feasible because headers are not yet available. In such cases, the second method is recommended. However, this approach necessitates invoking `setNavigatorContextProvider` on both the server and client sides, increasing the potential for user errors. To address this, a warning is issued if the setup is not correctly implemented. --- .../src/ssr/search-engine.test.tsx | 7 +++ .../commerce-engine/commerce-engine.ssr.ts | 26 +++++++- .../app/search-engine/search-engine.ssr.ts | 28 ++++++++- .../src/app/ssr-engine/types/core-engine.ts | 10 +++ packages/headless/src/ssr-commerce.index.ts | 1 + .../app/_lib/navigatorContextProvider.ts | 63 +++++++++++++++++++ .../headless-ssr-commerce/app/middleware.ts | 10 +++ 7 files changed, 141 insertions(+), 4 deletions(-) create mode 100644 packages/samples/headless-ssr-commerce/app/_lib/navigatorContextProvider.ts create mode 100644 packages/samples/headless-ssr-commerce/app/middleware.ts diff --git a/packages/headless-react/src/ssr/search-engine.test.tsx b/packages/headless-react/src/ssr/search-engine.test.tsx index a5f7538ffb9..26ebd2ef3f1 100644 --- a/packages/headless-react/src/ssr/search-engine.test.tsx +++ b/packages/headless-react/src/ssr/search-engine.test.tsx @@ -12,6 +12,7 @@ import {defineSearchEngine} from './search-engine.js'; describe('Headless react SSR utils', () => { let errorSpy: jest.SpyInstance; + const mockedNavigatorContextProvider = jest.fn(); const sampleConfig = { ...getSampleSearchEngineConfiguration(), analytics: {enabled: false}, // TODO: KIT-2585 Remove after analytics SSR support is added @@ -34,6 +35,7 @@ describe('Headless react SSR utils', () => { controllers, StaticStateProvider, HydratedStateProvider, + setNavigatorContextProvider, ...rest } = defineSearchEngine({ configuration: sampleConfig, @@ -46,6 +48,7 @@ describe('Headless react SSR utils', () => { useEngine, StaticStateProvider, HydratedStateProvider, + setNavigatorContextProvider, ].forEach((returnValue) => expect(typeof returnValue).toBe('function')); expect(controllers).toEqual({}); @@ -81,6 +84,7 @@ describe('Headless react SSR utils', () => { StaticStateProvider, HydratedStateProvider, controllers, + setNavigatorContextProvider, useEngine, } = engineDefinition; @@ -131,6 +135,7 @@ describe('Headless react SSR utils', () => { }); test('should render with StaticStateProvider', async () => { + setNavigatorContextProvider(mockedNavigatorContextProvider); const staticState = await fetchStaticState(); render( @@ -142,6 +147,7 @@ describe('Headless react SSR utils', () => { }); test('should hydrate results with HydratedStateProvider', async () => { + setNavigatorContextProvider(mockedNavigatorContextProvider); const staticState = await fetchStaticState(); const {engine, controllers} = await hydrateStaticState(staticState); @@ -159,6 +165,7 @@ describe('Headless react SSR utils', () => { let hydratedState: InferHydratedState; beforeEach(async () => { + setNavigatorContextProvider(mockedNavigatorContextProvider); staticState = await fetchStaticState(); hydratedState = await hydrateStaticState(staticState); }); diff --git a/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts b/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts index 2f7e55e9283..73e9ae811d6 100644 --- a/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts +++ b/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts @@ -6,6 +6,7 @@ import {stateKey} from '../../app/state-key'; import {buildProductListing} from '../../controllers/commerce/product-listing/headless-product-listing'; import type {Controller} from '../../controllers/controller/headless-controller'; import {createWaitForActionMiddleware} from '../../utils/utils'; +import {NavigatorContextProvider} from '../navigatorContextProvider'; import { buildControllerDefinitions, composeFunction, @@ -117,13 +118,21 @@ export function defineCommerceEngine< type HydrateStaticStateFromBuildResultParameters = Parameters; - const getOpts = () => { + const getOptions = () => { return engineOptions; }; + const setNavigatorContextProvider = ( + navigatorContextProvider: NavigatorContextProvider + ) => { + engineOptions.navigatorContextProvider = navigatorContextProvider; + }; + const build: BuildFunction = async (...[buildOptions]: BuildParameters) => { const engine = buildSSRCommerceEngine( - buildOptions?.extend ? await buildOptions.extend(getOpts()) : getOpts() + buildOptions?.extend + ? await buildOptions.extend(getOptions()) + : getOptions() ); const controllers = buildControllerDefinitions({ definitionsMap: (controllerDefinitions ?? {}) as TControllerDefinitions, @@ -140,6 +149,12 @@ export function defineCommerceEngine< const fetchStaticState: FetchStaticStateFunction = composeFunction( async (...params: FetchStaticStateParameters) => { + if (!getOptions().navigatorContextProvider) { + // TODO: KIT-3409 - implement a logger to log SSR warnings/errors + console.warn( + '[WARNING] Missing navigator context in server-side code. Make sure to set it with `setNavigatorContextProvider` before calling fetchStaticState()' + ); + } const buildResult = await build(...params); const staticState = await fetchStaticState.fromBuildResult({ buildResult, @@ -170,6 +185,12 @@ export function defineCommerceEngine< const hydrateStaticState: HydrateStaticStateFunction = composeFunction( async (...params: HydrateStaticStateParameters) => { + if (!getOptions().navigatorContextProvider) { + // TODO: KIT-3409 - implement a logger to log SSR warnings/errors + console.warn( + '[WARNING] Missing navigator context in client-side code. Make sure to set it with `setNavigatorContextProvider` before calling hydrateStaticState()' + ); + } const buildResult = await build(...(params as BuildParameters)); const staticState = await hydrateStaticState.fromBuildResult({ buildResult, @@ -198,5 +219,6 @@ export function defineCommerceEngine< build, fetchStaticState, hydrateStaticState, + setNavigatorContextProvider, }; } diff --git a/packages/headless/src/app/search-engine/search-engine.ssr.ts b/packages/headless/src/app/search-engine/search-engine.ssr.ts index 35ad3cd17be..56003a36cca 100644 --- a/packages/headless/src/app/search-engine/search-engine.ssr.ts +++ b/packages/headless/src/app/search-engine/search-engine.ssr.ts @@ -5,6 +5,7 @@ import {UnknownAction} from '@reduxjs/toolkit'; import type {Controller} from '../../controllers/controller/headless-controller'; import {LegacySearchAction} from '../../features/analytics/analytics-utils'; import {createWaitForActionMiddleware} from '../../utils/utils'; +import {NavigatorContextProvider} from '../navigatorContextProvider'; import { buildControllerDefinitions, composeFunction, @@ -108,11 +109,21 @@ export function defineSearchEngine< type HydrateStaticStateFromBuildResultParameters = Parameters; + const getOptions = () => { + return engineOptions; + }; + + const setNavigatorContextProvider = ( + navigatorContextProvider: NavigatorContextProvider + ) => { + engineOptions.navigatorContextProvider = navigatorContextProvider; + }; + const build: BuildFunction = async (...[buildOptions]: BuildParameters) => { const engine = buildSSRSearchEngine( buildOptions?.extend - ? await buildOptions.extend(engineOptions) - : engineOptions + ? await buildOptions.extend(getOptions()) + : getOptions() ); const controllers = buildControllerDefinitions({ definitionsMap: (controllerDefinitions ?? {}) as TControllerDefinitions, @@ -129,6 +140,12 @@ export function defineSearchEngine< const fetchStaticState: FetchStaticStateFunction = composeFunction( async (...params: FetchStaticStateParameters) => { + if (!getOptions().navigatorContextProvider) { + // TODO: KIT-3409 - implement a logger to log SSR warnings/errors + console.warn( + '[WARNING] Missing navigator context in server-side code. Make sure to set it with `setNavigatorContextProvider` before calling fetchStaticState()' + ); + } const buildResult = await build(...params); const staticState = await fetchStaticState.fromBuildResult({ buildResult, @@ -156,6 +173,12 @@ export function defineSearchEngine< const hydrateStaticState: HydrateStaticStateFunction = composeFunction( async (...params: HydrateStaticStateParameters) => { + if (!getOptions().navigatorContextProvider) { + // TODO: KIT-3409 - implement a logger to log SSR warnings/errors + console.warn( + '[WARNING] Missing navigator context in client-side code. Make sure to set it with `setNavigatorContextProvider` before calling hydrateStaticState()' + ); + } const buildResult = await build(...(params as BuildParameters)); const staticState = await hydrateStaticState.fromBuildResult({ buildResult, @@ -184,5 +207,6 @@ export function defineSearchEngine< build, fetchStaticState, hydrateStaticState, + setNavigatorContextProvider, }; } diff --git a/packages/headless/src/app/ssr-engine/types/core-engine.ts b/packages/headless/src/app/ssr-engine/types/core-engine.ts index 2a688ea99ae..ffabf9c9766 100644 --- a/packages/headless/src/app/ssr-engine/types/core-engine.ts +++ b/packages/headless/src/app/ssr-engine/types/core-engine.ts @@ -2,6 +2,7 @@ import {AnyAction} from '@reduxjs/toolkit'; import type {Controller} from '../../../controllers/controller/headless-controller'; import {CoreEngine, CoreEngineNext} from '../../engine'; import {EngineConfiguration} from '../../engine-configuration'; +import {NavigatorContextProvider} from '../../navigatorContextProvider'; import {Build} from './build'; import { ControllerDefinitionsMap, @@ -49,6 +50,15 @@ export interface EngineDefinition< AnyAction, InferControllerPropsMapFromDefinitions >; + /** + * Sets the navigator context provider. + * This provider is essential for retrieving navigation-related data such as referrer, userAgent, location, and clientId, which are crucial for handling both server-side and client-side API requests effectively. + * + * Note: The implementation specifics of the navigator context provider depend on the Node.js framework being utilized. It is the developer's responsibility to appropriately define and implement the navigator context provider to ensure accurate navigation context is available throughout the application. If the user fails to provide a navigator context provider, a warning will be logged either on the server or the browser console. + */ + setNavigatorContextProvider: ( + navigatorContextProvider: NavigatorContextProvider + ) => void; /** * Builds an engine and its controllers from an engine definition. */ diff --git a/packages/headless/src/ssr-commerce.index.ts b/packages/headless/src/ssr-commerce.index.ts index 80c5b56748d..554c0c9f4bc 100644 --- a/packages/headless/src/ssr-commerce.index.ts +++ b/packages/headless/src/ssr-commerce.index.ts @@ -36,6 +36,7 @@ export type { InferBuildResult, } from './app/ssr-engine/types/core-engine'; export type {LoggerOptions} from './app/logger'; +export type {NavigatorContext} from './app/navigatorContextProvider'; export type {LogLevel} from './app/logger'; diff --git a/packages/samples/headless-ssr-commerce/app/_lib/navigatorContextProvider.ts b/packages/samples/headless-ssr-commerce/app/_lib/navigatorContextProvider.ts new file mode 100644 index 00000000000..cb5ffa03b7e --- /dev/null +++ b/packages/samples/headless-ssr-commerce/app/_lib/navigatorContextProvider.ts @@ -0,0 +1,63 @@ +import {NavigatorContext} from '@coveo/headless/ssr-commerce'; +import type {ReadonlyHeaders} from 'next/dist/server/web/spec-extension/adapters/headers'; + +/** + * This class implements the NavigatorContext interface from Coveo's SSR commerce sub-package. + * It is designed to work within a Next.js environment, providing a way to extract + * navigation-related context from Next.js request headers. This context will then be + * pass to subsequent search requests. + */ +export class NextJsNavigatorContext implements NavigatorContext { + /** + * Initializes a new instance of the NextJsNavigatorContext class. + * @param headers The readonly headers from a Next.js request, providing access to request-specific data. + */ + constructor(private headers: ReadonlyHeaders) {} + + /** + * Retrieves the referrer URL from the request headers. + * Some browsers use 'referer' while others may use 'referrer'. + * @returns The referrer URL if available, otherwise undefined. + */ + get referrer() { + return this.headers.get('referer') || this.headers.get('referrer'); + } + + /** + * Retrieves the user agent string from the request headers. + * @returns The user agent string if available, otherwise undefined. + */ + get userAgent() { + return this.headers.get('user-agent'); + } + + /** + * Placeholder for the location property. Needs to be implemented based on the application's requirements. + * @returns Currently returns a 'TODO:' string. + */ + get location() { + return 'TODO:'; + } + + /** + * Fetches the unique client ID that was generated earlier by the middleware. + * @returns The client ID. + */ + get clientId() { + const clientId = this.headers.get('x-coveo-client-id'); + return clientId!; + } + + /** + * Marshals the navigation context into a format that can be used by Coveo's headless library. + * @returns An object containing clientId, location, referrer, and userAgent properties. + */ + get marshal(): NavigatorContext { + return { + clientId: this.clientId, + location: this.location, + referrer: this.referrer, + userAgent: this.userAgent, + }; + } +} diff --git a/packages/samples/headless-ssr-commerce/app/middleware.ts b/packages/samples/headless-ssr-commerce/app/middleware.ts new file mode 100644 index 00000000000..297df469aef --- /dev/null +++ b/packages/samples/headless-ssr-commerce/app/middleware.ts @@ -0,0 +1,10 @@ +import {NextRequest, NextResponse} from 'next/server'; + +export default function middleware(request: NextRequest) { + const response = NextResponse.next(); + const requestHeaders = new Headers(request.headers); + const uuid = crypto.randomUUID(); + requestHeaders.set('x-coveo-client-id', uuid); + response.headers.set('x-coveo-client-id', uuid); + return response; +} From 1db110c138d8f43a87f2e530fee00312e9c4c980 Mon Sep 17 00:00:00 2001 From: Alex Prudhomme <78121423+alexprudhomme@users.noreply.github.com> Date: Mon, 5 Aug 2024 09:50:22 -0400 Subject: [PATCH 14/23] ci: fix error in publish-pr-review-site action (#4242) https://coveord.atlassian.net/browse/KIT-3451 --- .../actions/publish-pr-review-site/action.yml | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/.github/actions/publish-pr-review-site/action.yml b/.github/actions/publish-pr-review-site/action.yml index be4993cbc9f..0cd3f8b7ce0 100644 --- a/.github/actions/publish-pr-review-site/action.yml +++ b/.github/actions/publish-pr-review-site/action.yml @@ -19,12 +19,29 @@ runs: token: ${{inputs.token}} - name: 'Setup branch' run: | - if [[ -z $(git ls-remote --heads origin refs/heads/${{github.event.pull_request.number}} | tr -s '[:blank:]') ]]; then - git switch -c "${{github.event.pull_request.number}}" + BRANCH_NAME="${{ github.event.pull_request.number }}" + + git fetch origin + + # Check if the branch exists on the remote + if git show-ref --verify --quiet "refs/remotes/origin/$BRANCH_NAME"; then + echo "Branch $BRANCH_NAME exists remotely. Checking out..." + + # If the branch exists locally, switch to it; otherwise, create a tracking branch + if git show-ref --verify --quiet "refs/heads/$BRANCH_NAME"; then + git switch "$BRANCH_NAME" + else + git switch --track "origin/$BRANCH_NAME" + fi + + # Reset the branch to match the latest commit from the main branch + git reset --hard origin/main else - git fetch origin "refs/heads/${{github.event.pull_request.number}}" - git switch "${{github.event.pull_request.number}}" - git reset --hard main + echo "Branch $BRANCH_NAME does not exist remotely. Creating a new branch..." + + # Create a new branch locally and push it to the remote + git switch -c "$BRANCH_NAME" + git push -u origin "$BRANCH_NAME" fi working-directory: prs shell: bash From a26184e4a4c17015b20f6e9271cbb8faf960b06a Mon Sep 17 00:00:00 2001 From: Nico Labarre Date: Mon, 5 Aug 2024 10:22:38 -0400 Subject: [PATCH 15/23] fix(commerce): namespace field suggestions to prevent clash with facet search (#4247) Since facets in a global search configuration are used to power both field suggestions and facets, it's possible that field suggestions for a field be active at the same time that a facet search for the same field is. This means that updating the text on a field suggestions controller would cause a facet's facet search values to be updated as well. This is because both the field suggestions and facet search were powered by the same state. To prevent this issue, I namespace the `facetId` used for field suggestions **only** on the facet search reducers (the regular, and category one). I opted for this approach instead of reversing the dependency on the facet search controller to read state from selectors since it would've lead to much more changes in the controllers. [CAPI-1201] [CAPI-1201]: https://coveord.atlassian.net/browse/CAPI-1201?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --- ...eadless-category-field-suggestions.test.ts | 15 ++++-- .../headless-category-field-suggestions.ts | 8 ++- ...adless-field-suggestions-generator.test.ts | 25 +++++---- .../headless-field-suggestions.test.ts | 23 ++++---- .../headless-field-suggestions.ts | 8 ++- ...egory-facet-search-request-builder.test.ts | 54 +++++++++++++++---- ...e-category-facet-search-request-builder.ts | 8 ++- .../commerce-facet-search-actions.ts | 18 +++++++ ...ce-regular-facet-search-request-builder.ts | 3 +- .../facets/facet-set/facet-set-slice.test.ts | 12 +++-- .../facets/facet-set/facet-set-slice.ts | 15 ++++-- .../category-facet-search-set-slice.ts | 6 ++- .../facet-search-reducer-helpers.ts | 20 +++++-- .../specific-facet-search-set-slice.ts | 32 +++++++---- 14 files changed, 178 insertions(+), 69 deletions(-) diff --git a/packages/headless/src/controllers/commerce/field-suggestions/headless-category-field-suggestions.test.ts b/packages/headless/src/controllers/commerce/field-suggestions/headless-category-field-suggestions.test.ts index a0bbfa9d5da..342a7e0a898 100644 --- a/packages/headless/src/controllers/commerce/field-suggestions/headless-category-field-suggestions.test.ts +++ b/packages/headless/src/controllers/commerce/field-suggestions/headless-category-field-suggestions.test.ts @@ -1,4 +1,7 @@ -import {executeCommerceFieldSuggest} from '../../../features/commerce/facets/facet-search-set/commerce-facet-search-actions'; +import { + executeCommerceFieldSuggest, + getFacetIdWithCommerceFieldSuggestionNamespace, +} from '../../../features/commerce/facets/facet-search-set/commerce-facet-search-actions'; import {commerceFacetSetReducer as commerceFacetSet} from '../../../features/commerce/facets/facet-set/facet-set-slice'; import {CategoryFacetRequest} from '../../../features/commerce/facets/facet-set/interfaces/request'; import {fieldSuggestionsOrderReducer as fieldSuggestionsOrder} from '../../../features/commerce/facets/field-suggestions-order/field-suggestions-order-slice'; @@ -33,7 +36,7 @@ describe('categoryFieldSuggestions', () => { let fieldSuggestions: CategoryFieldSuggestions; let options: CategoryFacetOptions; - function initFacet() { + function initFieldSuggestions() { engine = buildMockCommerceEngine(state); fieldSuggestions = buildCategoryFieldSuggestions(engine, options); } @@ -41,7 +44,9 @@ describe('categoryFieldSuggestions', () => { function setFacetRequest(config: Partial = {}) { const request = buildMockCommerceFacetRequest({facetId, ...config}); state.commerceFacetSet[facetId] = buildMockCommerceFacetSlice({request}); - state.categoryFacetSearchSet[facetId] = buildMockCategoryFacetSearch({ + state.categoryFacetSearchSet[ + getFacetIdWithCommerceFieldSuggestionNamespace(facetId) + ] = buildMockCategoryFacetSearch({ initialNumberOfValues: 5, }); } @@ -58,7 +63,7 @@ describe('categoryFieldSuggestions', () => { state = buildMockCommerceState(); setFacetRequest(); - initFacet(); + initFieldSuggestions(); }); it('adds correct reducers to engine', () => { @@ -72,7 +77,7 @@ describe('categoryFieldSuggestions', () => { it('should dispatch an #updateFacetSearch and #executeFieldSuggest action on #updateText', () => { fieldSuggestions.updateText('foo'); expect(updateFacetSearch).toHaveBeenCalledWith({ - facetId, + facetId: getFacetIdWithCommerceFieldSuggestionNamespace(facetId), query: 'foo', numberOfValues: 5, }); diff --git a/packages/headless/src/controllers/commerce/field-suggestions/headless-category-field-suggestions.ts b/packages/headless/src/controllers/commerce/field-suggestions/headless-category-field-suggestions.ts index 531851bccc3..091e1627d5a 100644 --- a/packages/headless/src/controllers/commerce/field-suggestions/headless-category-field-suggestions.ts +++ b/packages/headless/src/controllers/commerce/field-suggestions/headless-category-field-suggestions.ts @@ -5,6 +5,7 @@ import { CommerceEngineState, } from '../../../app/commerce-engine/commerce-engine'; import {stateKey} from '../../../app/state-key'; +import {getFacetIdWithCommerceFieldSuggestionNamespace} from '../../../features/commerce/facets/facet-search-set/commerce-facet-search-actions'; import {commerceFacetSetReducer as commerceFacetSet} from '../../../features/commerce/facets/facet-set/facet-set-slice'; import {fieldSuggestionsOrderReducer as fieldSuggestionsOrder} from '../../../features/commerce/facets/field-suggestions-order/field-suggestions-order-slice'; import {categoryFacetSearchSetReducer as categoryFacetSearchSet} from '../../../features/facets/facet-search-set/category/category-facet-search-set-slice'; @@ -81,9 +82,12 @@ export function buildCategoryFieldSuggestions( const {dispatch} = engine; + const namespacedFacetId = getFacetIdWithCommerceFieldSuggestionNamespace( + options.facetId + ); const facetSearch = buildCategoryFacetSearch(engine, { options: { - facetId: options.facetId, + facetId: namespacedFacetId, ...options.facetSearch, numberOfValues: 10, }, @@ -105,7 +109,7 @@ export function buildCategoryFieldSuggestions( const facetSearchStateSelector = createSelector( (state: CommerceEngineState) => - state.categoryFacetSearchSet[options.facetId], + state.categoryFacetSearchSet[namespacedFacetId], (facetSearch) => ({ isLoading: facetSearch.isLoading, moreValuesAvailable: facetSearch.response.moreValuesAvailable, diff --git a/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions-generator.test.ts b/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions-generator.test.ts index f20d4dddc17..b75ac8d9ff8 100644 --- a/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions-generator.test.ts +++ b/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions-generator.test.ts @@ -1,5 +1,6 @@ import {FacetSearchType} from '../../../api/commerce/facet-search/facet-search-request'; import {FieldSuggestionsFacet} from '../../../api/commerce/search/query-suggest/query-suggest-response'; +import {getFacetIdWithCommerceFieldSuggestionNamespace} from '../../../features/commerce/facets/facet-search-set/commerce-facet-search-actions'; import {fieldSuggestionsOrderReducer as fieldSuggestionsOrder} from '../../../features/commerce/facets/field-suggestions-order/field-suggestions-order-slice'; import {CommerceAppState} from '../../../state/commerce-app-state'; import {buildMockCategoryFacetSearch} from '../../../test/mock-category-facet-search'; @@ -38,16 +39,22 @@ describe('fieldSuggestionsGenerator', () => { function setFacetState(config: FieldSuggestionsFacet[] = []) { for (const facet of config) { + const namespacedFacetId = getFacetIdWithCommerceFieldSuggestionNamespace( + facet.facetId + ); state.fieldSuggestionsOrder.push(facet); - state.commerceFacetSet[facet.facetId] = { - request: buildMockCommerceFacetRequest({ - facetId: facet.facetId, - type: facet.type, - }), - }; - state.facetSearchSet[facet.facetId] = buildMockFacetSearch(); - state.categoryFacetSearchSet[facet.facetId] = - buildMockCategoryFacetSearch(); + if (facet.type === 'regular') { + state.facetSearchSet[namespacedFacetId] = buildMockFacetSearch(); + } else { + state.commerceFacetSet[facet.facetId] = { + request: buildMockCommerceFacetRequest({ + facetId: facet.facetId, + type: facet.type, + }), + }; + state.categoryFacetSearchSet[namespacedFacetId] = + buildMockCategoryFacetSearch(); + } } } diff --git a/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions.test.ts b/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions.test.ts index be836448924..52bd0fd8b90 100644 --- a/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions.test.ts +++ b/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions.test.ts @@ -1,12 +1,12 @@ -import {executeCommerceFieldSuggest} from '../../../features/commerce/facets/facet-search-set/commerce-facet-search-actions'; +import { + executeCommerceFieldSuggest, + getFacetIdWithCommerceFieldSuggestionNamespace, +} from '../../../features/commerce/facets/facet-search-set/commerce-facet-search-actions'; import {commerceFacetSetReducer as commerceFacetSet} from '../../../features/commerce/facets/facet-set/facet-set-slice'; -import {RegularFacetRequest} from '../../../features/commerce/facets/facet-set/interfaces/request'; import {fieldSuggestionsOrderReducer as fieldSuggestionsOrder} from '../../../features/commerce/facets/field-suggestions-order/field-suggestions-order-slice'; import {updateFacetSearch} from '../../../features/facets/facet-search-set/specific/specific-facet-search-actions'; import {specificFacetSearchSetReducer as facetSearchSet} from '../../../features/facets/facet-search-set/specific/specific-facet-search-set-slice'; import {CommerceAppState} from '../../../state/commerce-app-state'; -import {buildMockCommerceFacetRequest} from '../../../test/mock-commerce-facet-request'; -import {buildMockCommerceFacetSlice} from '../../../test/mock-commerce-facet-slice'; import {buildMockCommerceState} from '../../../test/mock-commerce-state'; import { buildMockCommerceEngine, @@ -33,16 +33,15 @@ describe('fieldSuggestions', () => { let fieldSuggestions: FieldSuggestions; let options: RegularFacetOptions; - function initFacet() { + function initFieldSuggestions() { engine = buildMockCommerceEngine(state); fieldSuggestions = buildFieldSuggestions(engine, options); } - function setFacetRequest(config: Partial = {}) { - state.commerceFacetSet[facetId] = buildMockCommerceFacetSlice({ - request: buildMockCommerceFacetRequest({facetId, ...config}), - }); - state.facetSearchSet[facetId] = buildMockFacetSearch({ + function setFacetSearchRequest() { + state.facetSearchSet[ + getFacetIdWithCommerceFieldSuggestionNamespace(facetId) + ] = buildMockFacetSearch({ initialNumberOfValues: 5, }); } @@ -58,9 +57,9 @@ describe('fieldSuggestions', () => { }; state = buildMockCommerceState(); - setFacetRequest(); + setFacetSearchRequest(); - initFacet(); + initFieldSuggestions(); }); it('adds correct reducers to engine', () => { diff --git a/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions.ts b/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions.ts index 6f0ed218cce..5a68ad16e5f 100644 --- a/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions.ts +++ b/packages/headless/src/controllers/commerce/field-suggestions/headless-field-suggestions.ts @@ -6,6 +6,7 @@ import { CommerceEngineState, } from '../../../app/commerce-engine/commerce-engine'; import {stateKey} from '../../../app/state-key'; +import {getFacetIdWithCommerceFieldSuggestionNamespace} from '../../../features/commerce/facets/facet-search-set/commerce-facet-search-actions'; import {commerceFacetSetReducer as commerceFacetSet} from '../../../features/commerce/facets/facet-set/facet-set-slice'; import {fieldSuggestionsOrderReducer as fieldSuggestionsOrder} from '../../../features/commerce/facets/field-suggestions-order/field-suggestions-order-slice'; import {specificFacetSearchSetReducer as facetSearchSet} from '../../../features/facets/facet-search-set/specific/specific-facet-search-set-slice'; @@ -99,8 +100,11 @@ export function buildFieldSuggestions( const {dispatch} = engine; + const namespacedFacetId = getFacetIdWithCommerceFieldSuggestionNamespace( + options.facetId + ); const facetSearch = buildRegularFacetSearch(engine, { - options: {facetId: options.facetId, ...options.facetSearch}, + options: {facetId: namespacedFacetId, ...options.facetSearch}, select: () => { dispatch(options.fetchProductsActionCreator()); }, @@ -121,7 +125,7 @@ export function buildFieldSuggestions( const controller = buildController(engine); const facetSearchStateSelector = createSelector( - (state: CommerceEngineState) => state.facetSearchSet[options.facetId], + (state: CommerceEngineState) => state.facetSearchSet[namespacedFacetId], (facetSearch) => ({ isLoading: facetSearch.isLoading, moreValuesAvailable: facetSearch.response.moreValuesAvailable, diff --git a/packages/headless/src/features/commerce/facets/facet-search-set/category/commerce-category-facet-search-request-builder.test.ts b/packages/headless/src/features/commerce/facets/facet-search-set/category/commerce-category-facet-search-request-builder.test.ts index 67b71df5232..58d2a0f17ac 100644 --- a/packages/headless/src/features/commerce/facets/facet-search-set/category/commerce-category-facet-search-request-builder.test.ts +++ b/packages/headless/src/features/commerce/facets/facet-search-set/category/commerce-category-facet-search-request-builder.test.ts @@ -9,21 +9,25 @@ import {buildMockCommerceState} from '../../../../../test/mock-commerce-state'; import {buildMockFacetSearchRequestOptions} from '../../../../../test/mock-facet-search-request-options'; import {buildMockNavigatorContextProvider} from '../../../../../test/mock-navigator-context-provider'; import {CategoryFacetValueRequest} from '../../facet-set/interfaces/request'; +import { + getFacetIdWithCommerceFieldSuggestionNamespace, + getFacetIdWithoutCommerceFieldSuggestionNamespace, +} from '../commerce-facet-search-actions'; import {buildCategoryFacetSearchRequest} from './commerce-category-facet-search-request-builder'; describe('#buildCategoryFacetSearchRequest', () => { let state: CommerceAppState; let navigatorContext: NavigatorContext; - let facetId: string; + const facetId = '1'; let query: string; let buildCommerceAPIRequestMock: jest.SpyInstance; beforeEach(() => { jest.clearAllMocks(); - facetId = '1'; query = 'test'; state = buildMockCommerceState(); + state.categoryFacetSearchSet[facetId] = buildMockCategoryFacetSearch({ options: {...buildMockFacetSearchRequestOptions(), query}, }); @@ -64,7 +68,28 @@ describe('#buildCategoryFacetSearchRequest', () => { ); }); - describe('returned object #ignorePaths property', () => { + describe.each([ + { + facetId: 'a_non_namespaced_facet_id', + }, + { + facetId: getFacetIdWithCommerceFieldSuggestionNamespace( + 'a_namespaced_facet_id' + ), + }, + ])('returned object #ignorePaths property', ({facetId}) => { + beforeEach(() => { + state.categoryFacetSearchSet[facetId] = buildMockCategoryFacetSearch({ + options: {...buildMockFacetSearchRequestOptions(), query}, + }); + + state.commerceFacetSet[ + getFacetIdWithoutCommerceFieldSuggestionNamespace(facetId) + ] = buildMockCommerceFacetSlice({ + request: buildMockCommerceFacetRequest({type: 'hierarchical'}), + }); + }); + it('when the facet request has no selected value, is an empty array', () => { const request = buildCategoryFacetSearchRequest( facetId, @@ -77,8 +102,12 @@ describe('#buildCategoryFacetSearchRequest', () => { }); it('when the facet request has a selected value with no ancestry, is an array with a single array containing the selected value', () => { - state.commerceFacetSet[facetId].request.values[0] = - buildMockCategoryFacetValue({state: 'selected', value: 'test'}); + state.commerceFacetSet[ + getFacetIdWithoutCommerceFieldSuggestionNamespace(facetId) + ].request.values[0] = buildMockCategoryFacetValue({ + state: 'selected', + value: 'test', + }); const request = buildCategoryFacetSearchRequest( facetId, state, @@ -89,15 +118,18 @@ describe('#buildCategoryFacetSearchRequest', () => { expect(request.ignorePaths).toStrictEqual([ [ ( - state.commerceFacetSet[facetId].request - .values[0] as CategoryFacetValueRequest + state.commerceFacetSet[ + getFacetIdWithoutCommerceFieldSuggestionNamespace(facetId) + ].request.values[0] as CategoryFacetValueRequest ).value, ], ]); }); it('when the facet request has a selected value with ancestry, is an array with a single array containing the selected value and its ancestors', () => { - state.commerceFacetSet[facetId].request.values[0] = + const nonNamespacedId = + getFacetIdWithoutCommerceFieldSuggestionNamespace(facetId); + state.commerceFacetSet[nonNamespacedId].request.values[0] = buildMockCategoryFacetValue({ value: 'test', children: [ @@ -122,15 +154,15 @@ describe('#buildCategoryFacetSearchRequest', () => { expect(request.ignorePaths).toStrictEqual([ [ ( - state.commerceFacetSet[facetId].request + state.commerceFacetSet[nonNamespacedId].request .values[0] as CategoryFacetValueRequest ).value, ( - state.commerceFacetSet[facetId].request + state.commerceFacetSet[nonNamespacedId].request .values[0] as CategoryFacetValueRequest ).children[0].value, ( - state.commerceFacetSet[facetId].request + state.commerceFacetSet[nonNamespacedId].request .values[0] as CategoryFacetValueRequest ).children[0].children[0].value, ], diff --git a/packages/headless/src/features/commerce/facets/facet-search-set/category/commerce-category-facet-search-request-builder.ts b/packages/headless/src/features/commerce/facets/facet-search-set/category/commerce-category-facet-search-request-builder.ts index 7c0dbc55b75..fd8c124a4d7 100644 --- a/packages/headless/src/features/commerce/facets/facet-search-set/category/commerce-category-facet-search-request-builder.ts +++ b/packages/headless/src/features/commerce/facets/facet-search-set/category/commerce-category-facet-search-request-builder.ts @@ -5,6 +5,7 @@ import { AnyFacetRequest, CategoryFacetRequest, } from '../../facet-set/interfaces/request'; +import {getFacetIdWithoutCommerceFieldSuggestionNamespace} from '../commerce-facet-search-actions'; import {StateNeededForCategoryFacetSearch} from './commerce-category-facet-search-state'; export const buildCategoryFacetSearchRequest = ( @@ -15,7 +16,10 @@ export const buildCategoryFacetSearchRequest = ( ): CategoryFacetSearchRequest => { const baseFacetQuery = state.categoryFacetSearchSet[facetId]!.options.query; const facetQuery = `*${baseFacetQuery}*`; - const categoryFacet = state.commerceFacetSet[facetId]?.request; + const categoryFacet = + state.commerceFacetSet[ + getFacetIdWithoutCommerceFieldSuggestionNamespace(facetId) + ]?.request; const path = categoryFacet && isCategoryFacetRequest(categoryFacet) ? categoryFacet && getPathToSelectedCategoryFacetItem(categoryFacet) @@ -40,7 +44,7 @@ export const buildCategoryFacetSearchRequest = ( url, accessToken, organizationId, - facetId, + facetId: getFacetIdWithoutCommerceFieldSuggestionNamespace(facetId), facetQuery, ignorePaths, trackingId, diff --git a/packages/headless/src/features/commerce/facets/facet-search-set/commerce-facet-search-actions.ts b/packages/headless/src/features/commerce/facets/facet-search-set/commerce-facet-search-actions.ts index 1b873983d75..e5adce226b2 100644 --- a/packages/headless/src/features/commerce/facets/facet-search-set/commerce-facet-search-actions.ts +++ b/packages/headless/src/features/commerce/facets/facet-search-set/commerce-facet-search-actions.ts @@ -114,3 +114,21 @@ export const isRegularFieldSuggestionsState = ( (facet) => facet.facetId === facetId && facet.type === 'regular' ); }; + +const commerceFieldSuggestionNamespace = 'field_suggestion:'; + +export function getFacetIdWithoutCommerceFieldSuggestionNamespace( + facetId: string +) { + return facetId.startsWith(commerceFieldSuggestionNamespace) + ? facetId.slice(commerceFieldSuggestionNamespace.length) + : facetId; +} + +export function getFacetIdWithCommerceFieldSuggestionNamespace( + facetId: string +): string { + return facetId.startsWith(commerceFieldSuggestionNamespace) + ? facetId + : `${commerceFieldSuggestionNamespace}${facetId}`; +} diff --git a/packages/headless/src/features/commerce/facets/facet-search-set/regular/commerce-regular-facet-search-request-builder.ts b/packages/headless/src/features/commerce/facets/facet-search-set/regular/commerce-regular-facet-search-request-builder.ts index ee88c1dcfc1..3ef3c04d7b3 100644 --- a/packages/headless/src/features/commerce/facets/facet-search-set/regular/commerce-regular-facet-search-request-builder.ts +++ b/packages/headless/src/features/commerce/facets/facet-search-set/regular/commerce-regular-facet-search-request-builder.ts @@ -1,6 +1,7 @@ import {CommerceFacetSearchRequest} from '../../../../../api/commerce/facet-search/facet-search-request'; import {NavigatorContext} from '../../../../../app/navigatorContextProvider'; import {buildCommerceAPIRequest} from '../../../common/actions'; +import {getFacetIdWithoutCommerceFieldSuggestionNamespace} from '../commerce-facet-search-actions'; import {StateNeededForRegularFacetSearch} from './commerce-regular-facet-search-state'; export const buildFacetSearchRequest = ( @@ -30,7 +31,7 @@ export const buildFacetSearchRequest = ( url, accessToken, organizationId, - facetId, + facetId: getFacetIdWithoutCommerceFieldSuggestionNamespace(facetId), facetQuery, trackingId, language, diff --git a/packages/headless/src/features/commerce/facets/facet-set/facet-set-slice.test.ts b/packages/headless/src/features/commerce/facets/facet-set/facet-set-slice.test.ts index 9aaa2a720dc..d88d05f6f13 100644 --- a/packages/headless/src/features/commerce/facets/facet-set/facet-set-slice.test.ts +++ b/packages/headless/src/features/commerce/facets/facet-set/facet-set-slice.test.ts @@ -64,6 +64,7 @@ import { toggleSelectDateFacetValue, updateDateFacetValues, } from '../date-facet/date-facet-actions'; +import {getFacetIdWithCommerceFieldSuggestionNamespace} from '../facet-search-set/commerce-facet-search-actions'; import { toggleExcludeNumericFacetValue, toggleSelectNumericFacetValue, @@ -688,16 +689,17 @@ describe('commerceFacetSetReducer', () => { ) ); expect(finalState).toEqual({ - regular_field: { + [getFacetIdWithCommerceFieldSuggestionNamespace('regular_field')]: { request: { initialNumberOfValues: 10, }, }, - hierarchical_field: { - request: { - initialNumberOfValues: 10, + [getFacetIdWithCommerceFieldSuggestionNamespace('hierarchical_field')]: + { + request: { + initialNumberOfValues: 10, + }, }, - }, }); }); }); diff --git a/packages/headless/src/features/commerce/facets/facet-set/facet-set-slice.ts b/packages/headless/src/features/commerce/facets/facet-set/facet-set-slice.ts index 57acc5397ee..1b35caf06af 100644 --- a/packages/headless/src/features/commerce/facets/facet-set/facet-set-slice.ts +++ b/packages/headless/src/features/commerce/facets/facet-set/facet-set-slice.ts @@ -40,7 +40,10 @@ import { toggleSelectDateFacetValue, updateDateFacetValues, } from '../date-facet/date-facet-actions'; -import {executeCommerceFieldSuggest} from '../facet-search-set/commerce-facet-search-actions'; +import { + executeCommerceFieldSuggest, + getFacetIdWithCommerceFieldSuggestionNamespace, +} from '../facet-search-set/commerce-facet-search-actions'; import { toggleExcludeNumericFacetValue, toggleSelectNumericFacetValue, @@ -81,7 +84,10 @@ export const commerceFacetSetReducer = createReducer( .addCase(fetchProductListing.fulfilled, handleQueryFulfilled) .addCase(executeSearch.fulfilled, handleQueryFulfilled) .addCase(executeCommerceFieldSuggest.fulfilled, (state, action) => - handleFieldSuggestionsFulfilled(state, action.payload.facetId) + handleFieldSuggestionsFulfilled( + state, + getFacetIdWithCommerceFieldSuggestionNamespace(action.payload.facetId) + ) ) .addCase(fetchQuerySuggestions.fulfilled, (state, action) => { if (!action.payload.fieldSuggestionsFacets) { @@ -89,7 +95,10 @@ export const commerceFacetSetReducer = createReducer( } for (const {facetId} of action.payload.fieldSuggestionsFacets) { - handleFieldSuggestionsFulfilled(state, facetId); + handleFieldSuggestionsFulfilled( + state, + getFacetIdWithCommerceFieldSuggestionNamespace(facetId) + ); } }) .addCase(toggleSelectFacetValue, (state, action) => { diff --git a/packages/headless/src/features/facets/facet-search-set/category/category-facet-search-set-slice.ts b/packages/headless/src/features/facets/facet-search-set/category/category-facet-search-set-slice.ts index 7dd249202db..24a8ff93f78 100644 --- a/packages/headless/src/features/facets/facet-search-set/category/category-facet-search-set-slice.ts +++ b/packages/headless/src/features/facets/facet-search-set/category/category-facet-search-set-slice.ts @@ -3,6 +3,7 @@ import {CategoryFacetSearchResponse} from '../../../../api/search/facet-search/c import { executeCommerceFacetSearch, executeCommerceFieldSuggest, + getFacetIdWithCommerceFieldSuggestionNamespace, } from '../../../commerce/facets/facet-search-set/commerce-facet-search-actions'; import {fetchProductListing} from '../../../commerce/product-listing/product-listing-actions'; import {fetchQuerySuggestions} from '../../../commerce/query-suggest/query-suggest-actions'; @@ -57,7 +58,10 @@ export const categoryFacetSearchSetReducer = createReducer( }) .addCase(executeCommerceFieldSuggest.rejected, (state, action) => { const {facetId} = action.meta.arg; - handleFacetSearchRejected(state, facetId); + handleFacetSearchRejected( + state, + getFacetIdWithCommerceFieldSuggestionNamespace(facetId) + ); }) .addCase(executeFacetSearch.rejected, (state, action) => { const facetId = action.meta.arg; diff --git a/packages/headless/src/features/facets/facet-search-set/facet-search-reducer-helpers.ts b/packages/headless/src/features/facets/facet-search-set/facet-search-reducer-helpers.ts index 3ca7c7785fc..024ca1e69b6 100644 --- a/packages/headless/src/features/facets/facet-search-set/facet-search-reducer-helpers.ts +++ b/packages/headless/src/features/facets/facet-search-set/facet-search-reducer-helpers.ts @@ -1,6 +1,7 @@ import {CommerceAPIResponse} from '../../../api/commerce/commerce-api-client'; import {FacetSearchRequestOptions} from '../../../api/search/facet-search/base/base-facet-search-request'; import {FacetSearchResponse} from '../../../api/search/facet-search/facet-search-response'; +import {getFacetIdWithCommerceFieldSuggestionNamespace} from '../../commerce/facets/facet-search-set/commerce-facet-search-actions'; import {FieldSuggestionsFacet} from '../../commerce/facets/field-suggestions-order/field-suggestions-order-state'; import {FacetSearchOptions} from './facet-search-request-options'; @@ -156,11 +157,17 @@ export function handleCommerceFacetFieldSuggestionsFulfilled< buildEmptyResponse: () => T ) { const {facetId, response} = payload; - let search = state[facetId]; + const namespacedFacetId = + getFacetIdWithCommerceFieldSuggestionNamespace(facetId); + let search = state[namespacedFacetId]; if (!search) { - handleFacetSearchRegistration(state, {facetId}, buildEmptyResponse); - search = state[facetId]; + handleFacetSearchRegistration( + state, + {facetId: namespacedFacetId}, + buildEmptyResponse + ); + search = state[namespacedFacetId]; } else if (search.requestId !== requestId) { return; } @@ -223,14 +230,17 @@ export function handleCommerceFetchQuerySuggestionsFulfilledForCategoryFacet< } for (const fieldSuggestionFacet of payload.fieldSuggestionsFacets) { + const namespacedFacetId = getFacetIdWithCommerceFieldSuggestionNamespace( + fieldSuggestionFacet.facetId + ); if ( - fieldSuggestionFacet.facetId in state || + namespacedFacetId in state || fieldSuggestionFacet.type !== 'hierarchical' ) { continue; } - state[fieldSuggestionFacet.facetId] = { + state[namespacedFacetId] = { options: { ...defaultFacetSearchOptions, query: payload.query ?? '', diff --git a/packages/headless/src/features/facets/facet-search-set/specific/specific-facet-search-set-slice.ts b/packages/headless/src/features/facets/facet-search-set/specific/specific-facet-search-set-slice.ts index 322abb686f9..98aadee0cc4 100644 --- a/packages/headless/src/features/facets/facet-search-set/specific/specific-facet-search-set-slice.ts +++ b/packages/headless/src/features/facets/facet-search-set/specific/specific-facet-search-set-slice.ts @@ -4,29 +4,32 @@ import {setView} from '../../../commerce/context/context-actions'; import { executeCommerceFacetSearch, executeCommerceFieldSuggest, + getFacetIdWithCommerceFieldSuggestionNamespace, } from '../../../commerce/facets/facet-search-set/commerce-facet-search-actions'; import {fetchProductListing} from '../../../commerce/product-listing/product-listing-actions'; import {fetchQuerySuggestions} from '../../../commerce/query-suggest/query-suggest-actions'; import {executeSearch as executeCommerceSearch} from '../../../commerce/search/search-actions'; import {executeSearch} from '../../../search/search-actions'; import { - handleFacetSearchRegistration, - handleFacetSearchUpdate, + handleCommerceFacetFieldSuggestionsFulfilled, + handleCommerceFacetSearchFulfilled, + handleCommerceFetchQuerySuggestionsFulfilledForRegularFacet, + handleFacetSearchClear, + handleFacetSearchFulfilled, handleFacetSearchPending, + handleFacetSearchRegistration, handleFacetSearchRejected, - handleFacetSearchFulfilled, - handleFacetSearchClear, handleFacetSearchSetClear, - handleCommerceFacetSearchFulfilled, - handleCommerceFacetFieldSuggestionsFulfilled, - handleCommerceFetchQuerySuggestionsFulfilledForRegularFacet, + handleFacetSearchUpdate, } from '../facet-search-reducer-helpers'; import { clearFacetSearch, executeFacetSearch, } from '../generic/generic-facet-search-actions'; -import {registerFacetSearch} from './specific-facet-search-actions'; -import {updateFacetSearch} from './specific-facet-search-actions'; +import { + registerFacetSearch, + updateFacetSearch, +} from './specific-facet-search-actions'; import {getFacetSearchSetInitialState} from './specific-facet-search-set-state'; export const specificFacetSearchSetReducer = createReducer( @@ -46,7 +49,11 @@ export const specificFacetSearchSetReducer = createReducer( }) .addCase(executeCommerceFieldSuggest.pending, (state, action) => { const {facetId} = action.meta.arg; - handleFacetSearchPending(state, facetId, action.meta.requestId); + handleFacetSearchPending( + state, + getFacetIdWithCommerceFieldSuggestionNamespace(facetId), + action.meta.requestId + ); }) .addCase(executeFacetSearch.pending, (state, action) => { const facetId = action.meta.arg; @@ -58,7 +65,10 @@ export const specificFacetSearchSetReducer = createReducer( }) .addCase(executeCommerceFieldSuggest.rejected, (state, action) => { const {facetId} = action.meta.arg; - handleFacetSearchRejected(state, facetId); + handleFacetSearchRejected( + state, + getFacetIdWithCommerceFieldSuggestionNamespace(facetId) + ); }) .addCase(executeFacetSearch.rejected, (state, action) => { const facetId = action.meta.arg; From afcbe2017de8c1a557dca5325de328b92d0884c7 Mon Sep 17 00:00:00 2001 From: Felix Perron-Brault Date: Mon, 5 Aug 2024 14:46:04 -0400 Subject: [PATCH 16/23] chore: add automatic sorting for tailwind classes (#4233) This PR adds `prettier-plugin-tailwindcss`, which is the [recommended solution](https://tailwindcss.com/blog/automatic-class-sorting-with-prettier) to sort tailwind classes. https://coveord.atlassian.net/browse/KIT-3439 --------- Co-authored-by: Alex Prudhomme <78121423+alexprudhomme@users.noreply.github.com> --- .prettierrc.js | 5 +- package-lock.json | 75 +++++++++++++++++++ package.json | 1 + .../atomic-commerce-products-per-page.pcss | 2 +- .../atomic-commerce-search-box.tsx | 4 +- .../atomic-commerce-category-facet.pcss | 4 +- .../atomic-commerce-facet-number-input.tsx | 4 +- .../atomic-product-children.tsx | 2 +- .../atomic-product-description.tsx | 2 +- .../atomic-product-price.tsx | 4 +- .../atomic-product-rating.tsx | 2 +- .../common/atomic-modal/atomic-modal.pcss | 2 +- .../common/atomic-modal/atomic-modal.tsx | 15 ++-- .../common/atomic-rating/atomic-rating.tsx | 2 +- .../common/breadbox/breadcrumb-clear-all.tsx | 2 +- .../common/breadbox/breadcrumb-container.tsx | 6 +- .../common/breadbox/breadcrumb-content.tsx | 2 +- .../common/breadbox/breadcrumb-show-less.tsx | 2 +- .../common/breadbox/breadcrumb-show-more.tsx | 2 +- .../atomic/src/components/common/carousel.tsx | 4 +- .../facets/category-facet/search-value.tsx | 8 +- .../common/facets/facet-common.pcss | 4 +- .../facet-container/facet-container.tsx | 2 +- .../facet-date-input/facet-date-input.tsx | 4 +- .../facets/facet-header/facet-header.tsx | 8 +- .../facet-number-input/facet-number-input.tsx | 4 +- .../facet-placeholder/facet-placeholder.tsx | 6 +- .../facet-search/facet-search-input.tsx | 8 +- .../facet-search/facet-search-matches.tsx | 4 +- .../facet-value-box/facet-value-box.pcss | 2 +- .../facet-value-box/facet-value-box.tsx | 4 +- .../facet-value-checkbox.pcss | 4 +- .../facet-value-exclude.tsx | 2 +- .../facet-value-label-highlight.tsx | 2 +- .../facet-value-link/facet-value-link.tsx | 2 +- .../atomic-citation/atomic-citation.tsx | 14 ++-- ...atomic-generated-answer-feedback-modal.tsx | 16 ++-- .../common/generated-answer/copy-button.tsx | 2 +- .../generated-answer/feedback-button.tsx | 2 +- .../generated-answer-common.tsx | 8 +- .../generated-markdown-content.pcss | 14 ++-- .../generated-markdown-content.tsx | 2 +- .../generated-text-content.tsx | 2 +- .../generated-answer/rephrase-buttons.tsx | 8 +- .../common/generated-answer/retry-prompt.tsx | 4 +- .../common/generated-answer/show-button.tsx | 4 +- .../generated-answer/source-citations.tsx | 4 +- .../styles/generated-answer.pcss | 6 +- .../src/components/common/iconButton.tsx | 6 +- .../image-carousel-indicators.tsx | 10 +-- .../common/image-carousel/image-carousel.tsx | 2 +- .../common/items-per-page/label.tsx | 2 +- .../components/common/load-more/button.tsx | 2 +- .../common/load-more/progress-bar.tsx | 4 +- .../components/common/load-more/summary.tsx | 2 +- .../src/components/common/no-items/cancel.tsx | 2 +- .../components/common/no-items/container.tsx | 2 +- .../common/no-items/magnifying-glass.tsx | 2 +- .../components/common/no-items/no-items.tsx | 4 +- .../src/components/common/no-items/tips.tsx | 2 +- .../components/common/pager/pager-buttons.tsx | 6 +- .../query-correction/trigger-correction.tsx | 4 +- .../common/query-error/container.tsx | 2 +- .../common/query-error/description.tsx | 2 +- .../components/common/query-error/details.tsx | 2 +- .../components/common/query-error/link.tsx | 2 +- .../common/query-error/show-more.tsx | 2 +- .../components/common/query-error/title.tsx | 2 +- .../components/common/query-summary/guard.tsx | 2 +- .../components/common/refine-modal/body.tsx | 2 +- .../components/common/refine-modal/button.tsx | 2 +- .../common/refine-modal/filters.tsx | 4 +- .../components/common/refine-modal/guard.tsx | 2 +- .../components/common/refine-modal/modal.tsx | 6 +- .../components/common/refine-modal/sort.tsx | 8 +- .../common/search-box/clear-button.tsx | 4 +- .../common/search-box/search-box.pcss | 4 +- .../common/search-box/search-input.tsx | 6 +- .../common/search-box/search-text-area.tsx | 8 +- .../common/search-box/submit-button.tsx | 4 +- .../search-box/text-area-clear-button.tsx | 6 +- .../search-box/text-area-submit-button.tsx | 6 +- ...atomic-smart-snippet-collapse-wrapper.pcss | 2 +- .../atomic-smart-snippet-collapse-wrapper.tsx | 4 +- ...tomic-smart-snippet-expandable-answer.pcss | 2 +- ...atomic-smart-snippet-expandable-answer.tsx | 4 +- .../atomic-smart-snippet-feedback-banner.tsx | 4 +- .../atomic-smart-snippet-feedback-modal.pcss | 4 +- .../smart-snippet-feedback-modal-common.tsx | 8 +- .../smart-snippet-suggestions-common.tsx | 10 +-- .../smart-snippet-common.tsx | 2 +- .../src/components/common/sort/container.tsx | 2 +- .../src/components/common/sort/guard.tsx | 2 +- .../src/components/common/sort/label.tsx | 2 +- .../src/components/common/sort/select.tsx | 2 +- .../common/suggestions/query-suggestions.tsx | 6 +- .../common/suggestions/recent-queries.tsx | 8 +- .../common/tab-manager/tab-button.tsx | 2 +- .../common/tab-manager/tab-dropdown.tsx | 4 +- .../src/components/common/tabs/tab-bar.tsx | 2 +- .../components/common/tabs/tab-popover.tsx | 8 +- .../atomic-insight-full-search-button.pcss | 4 +- .../atomic-insight-query-summary.tsx | 2 +- .../atomic-insight-refine-modal.tsx | 4 +- .../atomic-insight-result-action-bar.pcss | 4 +- .../atomic-insight-search-box.pcss | 10 +-- .../atomic-insight-search-box.tsx | 4 +- .../atomic-insight-tab.pcss | 2 +- .../result-lists/styles/list-display.pcss | 2 +- .../ipx/atomic-ipx-body/atomic-ipx-body.pcss | 4 +- .../ipx/atomic-ipx-body/atomic-ipx-body.tsx | 4 +- .../atomic-ipx-button/atomic-ipx-button.pcss | 4 +- .../atomic-ipx-refine-modal.tsx | 4 +- .../atomic-ipx-refine-toggle.pcss | 4 +- .../ipx/atomic-ipx-tab/atomic-ipx-tab.pcss | 2 +- .../atomic-notifications.tsx | 6 +- .../atomic-relevance-inspector.tsx | 4 +- .../atomic-result-table-placeholder.tsx | 14 ++-- .../atomic-results-per-page.pcss | 2 +- .../atomic-search-box/atomic-search-box.pcss | 2 +- .../atomic-search-box/atomic-search-box.tsx | 4 +- .../atomic-category-facet.pcss | 4 +- .../atomic-color-facet/atomic-color-facet.tsx | 2 +- .../facets/atomic-popover/atomic-popover.tsx | 14 ++-- .../atomic-rating-range-facet.tsx | 2 +- .../atomic-segmented-facet-scrollable.tsx | 6 +- .../atomic-segmented-facet.tsx | 4 +- .../color-facet-checkbox.pcss | 2 +- .../facet-segmented-value.tsx | 4 +- .../atomic-quickview-modal.pcss | 2 +- .../atomic-quickview-modal.tsx | 6 +- .../atomic-quickview-sidebar.tsx | 14 ++-- .../atomic-quickview/atomic-quickview.tsx | 2 +- .../atomic-result-badge.tsx | 4 +- .../quickview-iframe/quickview-iframe.tsx | 2 +- .../atomic-tab-manager/atomic-tab-manager.tsx | 2 +- packages/atomic/src/global/global.pcss | 24 +++--- .../pages/examples/commerce-website/cart.html | 2 +- .../examples/commerce-website/homepage.html | 2 +- .../commerce-website/listing-pants.html | 2 +- .../listing-surf-accessories.html | 2 +- .../commerce-website/listing-towels.html | 2 +- .../examples/commerce-website/search.html | 6 +- .../src/pages/CommerceSearchPage.tsx | 2 +- 144 files changed, 384 insertions(+), 310 deletions(-) diff --git a/.prettierrc.js b/.prettierrc.js index ef1aec7470c..1f0b0cf9eef 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -5,7 +5,10 @@ const decoratorPlugin = JSON.stringify([ /** @type {import('prettier').Config} */ module.exports = { - plugins: ['@trivago/prettier-plugin-sort-imports'], + plugins: [ + '@trivago/prettier-plugin-sort-imports', + 'prettier-plugin-tailwindcss', + ], bracketSpacing: false, singleQuote: true, trailingComma: 'es5', diff --git a/package-lock.json b/package-lock.json index ca2201ee909..f7dac3070c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -83,6 +83,7 @@ "nx": "19.0.4", "patch-package": "8.0.0", "prettier": "3.3.3", + "prettier-plugin-tailwindcss": "0.6.5", "react-syntax-highlighter": "15.5.0", "rimraf": "5.0.9", "semver": "7.6.3", @@ -47308,6 +47309,80 @@ "prettier": "^3.0.0" } }, + "node_modules/prettier-plugin-tailwindcss": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.5.tgz", + "integrity": "sha512-axfeOArc/RiGHjOIy9HytehlC0ZLeMaqY09mm8YCkMzznKiDkwFzOpBvtuhuv3xG5qB73+Mj7OCe2j/L1ryfuQ==", + "dev": true, + "engines": { + "node": ">=14.21.3" + }, + "peerDependencies": { + "@ianvs/prettier-plugin-sort-imports": "*", + "@prettier/plugin-pug": "*", + "@shopify/prettier-plugin-liquid": "*", + "@trivago/prettier-plugin-sort-imports": "*", + "@zackad/prettier-plugin-twig-melody": "*", + "prettier": "^3.0", + "prettier-plugin-astro": "*", + "prettier-plugin-css-order": "*", + "prettier-plugin-import-sort": "*", + "prettier-plugin-jsdoc": "*", + "prettier-plugin-marko": "*", + "prettier-plugin-organize-attributes": "*", + "prettier-plugin-organize-imports": "*", + "prettier-plugin-sort-imports": "*", + "prettier-plugin-style-order": "*", + "prettier-plugin-svelte": "*" + }, + "peerDependenciesMeta": { + "@ianvs/prettier-plugin-sort-imports": { + "optional": true + }, + "@prettier/plugin-pug": { + "optional": true + }, + "@shopify/prettier-plugin-liquid": { + "optional": true + }, + "@trivago/prettier-plugin-sort-imports": { + "optional": true + }, + "@zackad/prettier-plugin-twig-melody": { + "optional": true + }, + "prettier-plugin-astro": { + "optional": true + }, + "prettier-plugin-css-order": { + "optional": true + }, + "prettier-plugin-import-sort": { + "optional": true + }, + "prettier-plugin-jsdoc": { + "optional": true + }, + "prettier-plugin-marko": { + "optional": true + }, + "prettier-plugin-organize-attributes": { + "optional": true + }, + "prettier-plugin-organize-imports": { + "optional": true + }, + "prettier-plugin-sort-imports": { + "optional": true + }, + "prettier-plugin-style-order": { + "optional": true + }, + "prettier-plugin-svelte": { + "optional": true + } + } + }, "node_modules/pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", diff --git a/package.json b/package.json index e7031a4eb17..44bd9beabe0 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,7 @@ "nx": "19.0.4", "patch-package": "8.0.0", "prettier": "3.3.3", + "prettier-plugin-tailwindcss": "0.6.5", "react-syntax-highlighter": "15.5.0", "rimraf": "5.0.9", "semver": "7.6.3", diff --git a/packages/atomic/src/components/commerce/atomic-commerce-products-per-page/atomic-commerce-products-per-page.pcss b/packages/atomic/src/components/commerce/atomic-commerce-products-per-page/atomic-commerce-products-per-page.pcss index ba9ae1fb155..d8f29ba58f2 100644 --- a/packages/atomic/src/components/commerce/atomic-commerce-products-per-page/atomic-commerce-products-per-page.pcss +++ b/packages/atomic/src/components/commerce/atomic-commerce-products-per-page/atomic-commerce-products-per-page.pcss @@ -1,5 +1,5 @@ @import '../../../global/global.pcss'; .btn-page { - @apply w-10 h-10; + @apply h-10 w-10; } diff --git a/packages/atomic/src/components/commerce/atomic-commerce-search-box/atomic-commerce-search-box.tsx b/packages/atomic/src/components/commerce/atomic-commerce-search-box/atomic-commerce-search-box.tsx index cb3c8ffbd3e..2e5f573745d 100644 --- a/packages/atomic/src/components/commerce/atomic-commerce-search-box/atomic-commerce-search-box.tsx +++ b/packages/atomic/src/components/commerce/atomic-commerce-search-box/atomic-commerce-search-box.tsx @@ -535,7 +535,7 @@ export class AtomicCommerceSearchBox ? 'suggestions-double-list' : 'suggestions-single-list' }`} - class={`flex w-full z-10 absolute left-0 top-full rounded-md bg-background border border-neutral ${ + class={`bg-background border-neutral absolute left-0 top-full z-10 flex w-full rounded-md border ${ this.suggestionManager.hasSuggestions && this.isExpanded && !this.isSearchDisabledForEndUser(this.searchBoxState.value) @@ -604,7 +604,7 @@ export class AtomicCommerceSearchBox ); diff --git a/packages/atomic/src/components/commerce/facets/atomic-commerce-category-facet/atomic-commerce-category-facet.pcss b/packages/atomic/src/components/commerce/facets/atomic-commerce-category-facet/atomic-commerce-category-facet.pcss index ceeb35f0691..b7a0b473a13 100644 --- a/packages/atomic/src/components/commerce/facets/atomic-commerce-category-facet/atomic-commerce-category-facet.pcss +++ b/packages/atomic/src/components/commerce/facets/atomic-commerce-category-facet/atomic-commerce-category-facet.pcss @@ -11,9 +11,9 @@ [part~='all-categories-button'], [part~='parent-button'] { - @apply w-full py-2.5 pr-2 pl-7 text-left relative flex items-center; + @apply relative flex w-full items-center py-2.5 pl-7 pr-2 text-left; } [part~='back-arrow'] { - @apply h-5 w-5 absolute left-1; + @apply absolute left-1 h-5 w-5; } diff --git a/packages/atomic/src/components/commerce/facets/facet-number-input/atomic-commerce-facet-number-input.tsx b/packages/atomic/src/components/commerce/facets/facet-number-input/atomic-commerce-facet-number-input.tsx index 714956e069b..757d7d504a4 100644 --- a/packages/atomic/src/components/commerce/facets/facet-number-input/atomic-commerce-facet-number-input.tsx +++ b/packages/atomic/src/components/commerce/facets/facet-number-input/atomic-commerce-facet-number-input.tsx @@ -79,7 +79,7 @@ export class FacetNumberInput { return (
{ e.preventDefault(); @@ -133,7 +133,7 @@ export class FacetNumberInput { style="outline-primary" type="submit" part="input-apply-button" - class="p-2.5 flex-none truncate" + class="flex-none truncate p-2.5" ariaLabel={applyAria} text={apply} > diff --git a/packages/atomic/src/components/commerce/product-template-components/atomic-product-children/atomic-product-children.tsx b/packages/atomic/src/components/commerce/product-template-components/atomic-product-children/atomic-product-children.tsx index 43f1e5ca7e2..61460ba9135 100644 --- a/packages/atomic/src/components/commerce/product-template-components/atomic-product-children/atomic-product-children.tsx +++ b/packages/atomic/src/components/commerce/product-template-components/atomic-product-children/atomic-product-children.tsx @@ -146,7 +146,7 @@ export class AtomicProductChildren private renderLabel() { return ( -
+
diff --git a/packages/atomic/src/components/commerce/product-template-components/atomic-product-description/atomic-product-description.tsx b/packages/atomic/src/components/commerce/product-template-components/atomic-product-description/atomic-product-description.tsx index b827878c3f9..e00153f0ba8 100644 --- a/packages/atomic/src/components/commerce/product-template-components/atomic-product-description/atomic-product-description.tsx +++ b/packages/atomic/src/components/commerce/product-template-components/atomic-product-description/atomic-product-description.tsx @@ -128,7 +128,7 @@ export class AtomicProductDescription > {' '} {this.bindings.i18n.t('show-more')} diff --git a/packages/atomic/src/components/commerce/product-template-components/atomic-product-price/atomic-product-price.tsx b/packages/atomic/src/components/commerce/product-template-components/atomic-product-price/atomic-product-price.tsx index cadc6ebcb31..c224720fa78 100644 --- a/packages/atomic/src/components/commerce/product-template-components/atomic-product-price/atomic-product-price.tsx +++ b/packages/atomic/src/components/commerce/product-template-components/atomic-product-price/atomic-product-price.tsx @@ -46,14 +46,14 @@ export class AtomicProductPrice return (
{hasPromotionalPrice && ( {this.ratingDetails !== null && ( - + ({this.ratingDetails}) )} diff --git a/packages/atomic/src/components/common/atomic-modal/atomic-modal.pcss b/packages/atomic/src/components/common/atomic-modal/atomic-modal.pcss index ccf13faa23b..d4026b1094e 100644 --- a/packages/atomic/src/components/common/atomic-modal/atomic-modal.pcss +++ b/packages/atomic/src/components/common/atomic-modal/atomic-modal.pcss @@ -75,7 +75,7 @@ atomic-focus-trap { } [part='body-wrapper'] { - @apply px-6 pt-8 pb-5; + @apply px-6 pb-5 pt-8; } [part='footer-wrapper'] { diff --git a/packages/atomic/src/components/common/atomic-modal/atomic-modal.tsx b/packages/atomic/src/components/common/atomic-modal/atomic-modal.tsx index dc3d09937e2..2387dbd9f44 100644 --- a/packages/atomic/src/components/common/atomic-modal/atomic-modal.tsx +++ b/packages/atomic/src/components/common/atomic-modal/atomic-modal.tsx @@ -140,16 +140,14 @@ export class AtomicModal implements InitializableComponent { const Content = () => (
this.animationEnded.emit()} ref={(ref) => (this.animatableContainer = ref)} >
@@ -158,7 +156,7 @@ export class AtomicModal implements InitializableComponent {
{