From 4ea115af5f7c8aab4e12dd83253fccb97f67a731 Mon Sep 17 00:00:00 2001 From: Kyle Wright <30636085+sf-kyle-wright@users.noreply.github.com> Date: Mon, 15 Sep 2025 18:11:49 -0400 Subject: [PATCH] @W-19593698 Search Enhancements (#3271) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * W-18981837 search enhancements v1 moved branch (#2890) * W-18981837 search enhancements v1 moved branch * W-18981837 search enhancements v1 moved branch * @W-18981744 Feature/search feature partity merge develop 8 1 (#2969) * Update CHANGELOG.md * bump versions * Bump versions in mcp server * Initial Commit Add initGit option Update readme for create app tool Always use latest for create app npx package version. * Change when git init is run also added basic gitignore for non-extension projects * Update CHANGELOG.md * Fix tests * Rename pwa-kit-mcp project * Update server.js * Include bin entry in package.json * Fix dependencies etc * Add example with output dir * Punctuation fix in readme * Update program.json PR feedback on description. Signed-off-by: Ben Chypak * Rename package to pwa-kit-mcp * Update server json in readme * Update readme and bump version * Ensure the latest version is used in readme example * Version align the mcp server with the platform version * Use --initGit flag and test for .git/ creation * Add flag both commands for template and presets * Ensure gitignore is created * Ensure default command includes --initGit * Update base command * qa prompt * update * Bump versions * Clean up changelogs * Update usid expiry to match refresh_token expiry * Linting fix * Update changelog * Better prompting for cursor and create app program * Update packages/commerce-sdk-react/src/auth/index.ts Co-authored-by: vcua-mobify <47404250+vcua-mobify@users.noreply.github.com> Signed-off-by: Jainam Sheth <99490559+shethj@users.noreply.github.com> * Add unit tests for usid expiry * Remove overlapping test cases * bump version * bump develop to next dev version * Update changelogs * Change version number * Update readme * Move tools to tools folder * Clean up files/folders, create new tool folder. * Lint * A11y tests - Handle dynamic aria labels generated by chakra UI (#2864) * Handle dynamic aria labels generated by chakra UI * improve sanitization --------- Co-authored-by: Alex Vuong * initial implementation of create page mcp tool * fix * support open url * fix the import * update README and changelog * Modified create page mcp tool * Added Image Componentsupport to Create page MCP tool * Fixed failing tests * Added new and fixed failing tests and corrected linting issues * Added new test and minor update to system prompt * Updated image component functionality * Updated system prompt * Fixed routes issue * Added tests and comments * Fixed merge conflicts and moved cursor to retail-react-app * skip changelog * Removed unused const * Addressed comments * Update reading expiry time for usid * Removed the cursor rule * Update codeowners file to include ownership for specific features (#2921) * Update codeowners * @W-18895609: 🚀 Shopper Agent Experience v3.0 (#2658) * feat: shopper agent experience v3.0 * fix: enabling streaming (#2911) * Committing for Rules and cleanup * skip changelog * Fixed tests * Lint fix * Addressed comments * Removed the flex hardcode in log message * Fixing Page Tool * Fixing Linter * Updating the Page tool * Fix eslint discoverability in the monorepo (@W-19068681@) (#2927) * Attempt at fixing eslint discoverability in the monorepo * Support other packages * Another approach: centralize the eslint dependencies * Add a script to sync eslint stuffs to the root * Update package lock files * Move timing of script to preinstall * Update package lock files * No need for this documentation * Empty commit * Simplify the script's logic * Reverting changes for Page tool * Added comment back * Reverting the test change * @W-19179026: disable streaming responses (#2964) * @W-18981744 increase packages size CI build failing --------- Signed-off-by: Ben Chypak Signed-off-by: Jainam Sheth <99490559+shethj@users.noreply.github.com> Co-authored-by: vcua-mobify Co-authored-by: Ben Chypak Co-authored-by: Ben Chypak Co-authored-by: Shailesh Zirpe Co-authored-by: Jainam Sheth Co-authored-by: Jainam Sheth <99490559+shethj@users.noreply.github.com> Co-authored-by: vcua-mobify <47404250+vcua-mobify@users.noreply.github.com> Co-authored-by: Alex Vuong Co-authored-by: Shailesh Zirpe <42553862+szirpesf@users.noreply.github.com> Co-authored-by: wei-liu Co-authored-by: atekur Co-authored-by: Aditya Tekur <126732792+aditek-sf@users.noreply.github.com> Co-authored-by: Ujwala <59447810+unandyala@users.noreply.github.com> Co-authored-by: Avinash Kasipathy Co-authored-by: snilakandan Co-authored-by: Vincent Marta Co-authored-by: snilakandan13 <119348013+snilakandan13@users.noreply.github.com> * @18981837 search suggestions enhancements v2 (#2901) * @W-18981837 UX search changes * @W-18981837 search suggestion * @W-18981837 view all button * W-18981837 search suggestions badges impl * W-18981837 new tests and lint * W-18981837 changelog * @W-18981837 translations * W-18981837 bundlesize ci error * @W-18981837 only support string and boolean * W-18981837 remove badges * W-18981837 dynamic image * @W-18981744 adjust widths * @W-18981837 lint fix * @W-18981837 expand required for images * W-18981858 add tests and fix image/desc spacing issue (#3049) * W-18981858 add tests and fix image/desc spacing issue * W-18981858 fix linting * W-18981858 chakra for widths * W-18981858 chakra for widths * @W-19396339 merge develop to feature (#3153) * Update CHANGELOG.md * bump versions * Bump versions in mcp server * Initial Commit Add initGit option Update readme for create app tool Always use latest for create app npx package version. * Change when git init is run also added basic gitignore for non-extension projects * Update CHANGELOG.md * Fix tests * Rename pwa-kit-mcp project * Update server.js * Include bin entry in package.json * Fix dependencies etc * Add example with output dir * Punctuation fix in readme * Update program.json PR feedback on description. Signed-off-by: Ben Chypak * Rename package to pwa-kit-mcp * Update server json in readme * Update readme and bump version * Ensure the latest version is used in readme example * Version align the mcp server with the platform version * Use --initGit flag and test for .git/ creation * Add flag both commands for template and presets * Ensure gitignore is created * Ensure default command includes --initGit * Update base command * qa prompt * update * Bump versions * Clean up changelogs * Update usid expiry to match refresh_token expiry * Linting fix * Update changelog * Better prompting for cursor and create app program * Update packages/commerce-sdk-react/src/auth/index.ts Co-authored-by: vcua-mobify <47404250+vcua-mobify@users.noreply.github.com> Signed-off-by: Jainam Sheth <99490559+shethj@users.noreply.github.com> * Add unit tests for usid expiry * Remove overlapping test cases * bump version * bump develop to next dev version * Update changelogs * Change version number * Update readme * Move tools to tools folder * Clean up files/folders, create new tool folder. * Lint * A11y tests - Handle dynamic aria labels generated by chakra UI (#2864) * Handle dynamic aria labels generated by chakra UI * improve sanitization --------- Co-authored-by: Alex Vuong * initial implementation of create page mcp tool * fix * support open url * fix the import * update README and changelog * Modified create page mcp tool * Added Image Componentsupport to Create page MCP tool * Fixed failing tests * Added new and fixed failing tests and corrected linting issues * Added new test and minor update to system prompt * Updated image component functionality * Updated system prompt * Fixed routes issue * Added tests and comments * Fixed merge conflicts and moved cursor to retail-react-app * skip changelog * Removed unused const * Addressed comments * Update reading expiry time for usid * Removed the cursor rule * Update codeowners file to include ownership for specific features (#2921) * Update codeowners * @W-18895609: 🚀 Shopper Agent Experience v3.0 (#2658) * feat: shopper agent experience v3.0 * fix: enabling streaming (#2911) * Committing for Rules and cleanup * skip changelog * Fixed tests * Remove reference to claude and clean up readme * Update CHANGELOG.md * Lint fix * Addressed comments * Make development section more robust. * Removed the flex hardcode in log message * Fixing Page Tool * Fixing Linter * Updating the Page tool * Fix eslint discoverability in the monorepo (@W-19068681@) (#2927) * Attempt at fixing eslint discoverability in the monorepo * Support other packages * Another approach: centralize the eslint dependencies * Add a script to sync eslint stuffs to the root * Update package lock files * Move timing of script to preinstall * Update package lock files * No need for this documentation * Empty commit * Simplify the script's logic * Reverting changes for Page tool * Added comment back * Update packages/pwa-kit-mcp/README.md Co-authored-by: Katia Hage Signed-off-by: Ben Chypak * Update packages/pwa-kit-mcp/README.md Co-authored-by: Katia Hage Signed-off-by: Ben Chypak * Update packages/pwa-kit-mcp/README.md Co-authored-by: Katia Hage Signed-off-by: Ben Chypak * Update packages/pwa-kit-mcp/README.md Co-authored-by: Katia Hage Signed-off-by: Ben Chypak * Update packages/pwa-kit-mcp/README.md Co-authored-by: Katia Hage Signed-off-by: Ben Chypak * Update packages/pwa-kit-mcp/README.md Co-authored-by: Katia Hage Signed-off-by: Ben Chypak * Fix link * Reverting the test change * Update package description * Remove folder structure section * @W-19179026: disable streaming responses (#2964) * Explicitly added example of what the path to your project might look like. * Update packages/pwa-kit-mcp/README.md Co-authored-by: Katia Hage Signed-off-by: Ben Chypak * @W-19248293: logMCPMessage is broken * @W-19248293: logMCPMessage is broken * fix lint errors * Committing for converting the createNewcomponent to use prompts instead of sessions and state * Committing for server.js * Committing for server.js * Committing for server.js * Review comments * @W-19251796 MCP a11y performance tool format fix (#3003) * Committing for unit tests * @W-19251739 fix create-new-page-tool components import path * @W-19183433: Add code owners validation to the CI pipeline (#2939) * Update codeowners * Update codeowners * Update codeowners * Add codeowners validator to the pipeline * codeowners validator * codeowners validator * codeowners validator * codeowners validator * codeowners validator * codeowners validator --------- Co-authored-by: Brian Feister <47546998+bfeister@users.noreply.github.com> * fix import when extensible is on * update change log * Initial Commit * remove bug fix change logs * add inputs for absoulte path and not rely on env variables * Normalize path separators to forward slashes for ES6 imports * Extra check to disallow trusted-system request from proxy * Added GIT version control MCP tool * Initial Commit * Review comments * Fixing tests * Review comments * Review comments * Update CHANGELOG.md * Fix test description * Move the statements around * Test updates * Update the string so that we can explicitly catch /trusted-system/token * Add robots.txt to mrt-reference-app. * Align versions with develop branch expectations * Update CHANGELOG.md * Move trusted agent exception above the generic case * Create agent_checkout_rebase_action.yml (#3027) Signed-off-by: Nishant Sethunath * [CI workflow] automate generating, configurating, and deploying build from `develop` branch (@W-19170135@) (#3058) * Add new performance metrics workflow * Add comment * Disable some steps that are not needed * Update todos * Generate the project * Avoid dirtying workspace and remove unnecessary step * Tweak parameters * Allow for internal short codes * Align with the order of answers to the extensibility question * Create update-csp-directives.js * Rename script for clarity * Schedule it to run before the performance tests * Move constant into the config file * Move CSP directives into the config file * Make sure the corresponding .hbs files are up to date * Now scripts looks for the config file * Fix linting errors * Add auto lint fixing * For backward compatibility * New step to add CSP directives * Log where the file is at * Add a new script * Address a few todos * Allow updating property with the same value * Finish and clean up the workflow * Move scripts and remove default value for config path * No longer assume default value for config path And no longer preserving comments * No longer support null, undefined values * Delete unnecessary comments * Revert changes in retail-react-app * Create a new generator preset * Revert changes to the generator * Add csp directives to ssr.js.hbs * Update app/constants.js files to support the new preset * No longer needing these scripts * Workflow now uses the new generator preset * No need to validate app in this case * Add the preset to more files * Revert some changes and keep one change * Delete extra space * Empty commit * Simplify the handlebar template logic * No more hardcoding the `generated-projects` path * Move feature toggle for partial hydration into the config file * Remove unnecessary tests * Update CHANGELOG.md * Make sure all ssr.js related files are in sync * Fix linting error I wanted to preserve the comma at the end of the line because otherwise, I'm worried that it's easy to overlook it later. --------- Co-authored-by: Jainam Sheth * Remove specific user not found message * @W-18905383@ Decouple lint and bundle size GA workflows (#3084) * Extract lint and bundle size workflows * test: lower bundle size limit to test CI failure This is a minimal change to verify the new bundle-size workflow correctly fails when bundle size exceeds limits. Will be reverted. * revert: restore original bundle size limit to 62 kB Reverts the test change that lowered the bundle size limit from 62 kB to 50 kB. The CI workflow decoupling test was successful. * test: add unused variable to trigger lint error Added an unused variable to test that the lint workflow fails correctly while the test workflow continues to run independently. * fix: remove test lint error Removed the unused variable that was added to test lint workflow failure. The lint workflow decoupling test was successful. * test: add undefined variable to trigger actual lint error Added undefined variable reference that causes ESLint 'no-undef' error, not just a warning. This should cause the lint workflow to fail. * revert: restore main.jsx to original clean state Removed both the unused variable and undefined variable that were added for testing lint workflow failure. File is now back to original state. * clean: remove unused IS_NOT_FORK env variable from bundle-size workflow The IS_NOT_FORK environment variable was defined but never used in any conditional statements within the bundle-size workflow. Removing it simplifies the workflow and allows bundle size checks to run on all PRs including those from forks, which is beneficial for code quality. * clean: remove unused IS_NOT_FORK env variable from lint workflow The IS_NOT_FORK environment variable was defined but never used in any conditional statements within the lint workflow. Removing it simplifies the workflow and allows linting checks to run on all PRs including those from forks, which is beneficial for code quality. * Modify user not found responses from SLAS * Make the mcp server indipendent * User not found returns 200 * Handling for responses with no body * CI performance workflow: fix the deploy step (@W-19170135@) (#3091) * CI perf workflow: fix the deploy step Override the location of the .mobify credential file * More robust fix * Refactor the flags * Add whitespace * Update agent_checkout_rebase_action.yml Signed-off-by: Nishant Sethunath * Update agent_checkout_rebase_action.yml Signed-off-by: Nishant Sethunath * Fix how 403s are sent now that selfHandleResponse is true * Add test * Remove unused message * Changelog updates * Update translations * Remove test case that is no longer needed * Update agent_checkout_rebase_action.yml Signed-off-by: Nishant Sethunath * Minor Cleanup * CI perf workflow: fix the deploy step.. take two (@W-19345469@) (#3115) * CI perf workflow: fix the deploy step Override the location of the .mobify credential file * More robust fix * Refactor the flags * Add whitespace * Update credentials * Add Slack notification * Apply feedback * lint * Lockdown chakra-ui version to 2.6.0 * Update changelog * Pin v2.7.0 * Bump vendor bundle size * W-19396339 add back in tests that were accidentally removed in conflict * W-19396339 lint fix * W-19396339 package size --------- Signed-off-by: Ben Chypak Signed-off-by: Jainam Sheth <99490559+shethj@users.noreply.github.com> Signed-off-by: Nishant Sethunath Co-authored-by: vcua-mobify Co-authored-by: Ben Chypak Co-authored-by: Ben Chypak Co-authored-by: Shailesh Zirpe Co-authored-by: Jainam Sheth Co-authored-by: Jainam Sheth <99490559+shethj@users.noreply.github.com> Co-authored-by: vcua-mobify <47404250+vcua-mobify@users.noreply.github.com> Co-authored-by: Alex Vuong Co-authored-by: Shailesh Zirpe <42553862+szirpesf@users.noreply.github.com> Co-authored-by: wei-liu Co-authored-by: atekur Co-authored-by: Aditya Tekur <126732792+aditek-sf@users.noreply.github.com> Co-authored-by: Ujwala <59447810+unandyala@users.noreply.github.com> Co-authored-by: Avinash Kasipathy Co-authored-by: snilakandan Co-authored-by: Vincent Marta Co-authored-by: Katia Hage Co-authored-by: snilakandan13 <119348013+snilakandan13@users.noreply.github.com> Co-authored-by: patricksullivansf <100441292+patricksullivansf@users.noreply.github.com> Co-authored-by: Yuming Hsieh Co-authored-by: Brian Feister <47546998+bfeister@users.noreply.github.com> Co-authored-by: jitendra.bothra Co-authored-by: jbothra-sf <152039392+jbothra-sf@users.noreply.github.com> Co-authored-by: Nishant Sethunath Co-authored-by: Adam Raya * @W-19560496 sync feature with develop (#3257) * Modify user not found responses from SLAS * Update MRT API secret * Remove stringify from fetch body * remove unnecessary change * Fix json formatting for payload * Handle bash escaping * Fix command * FIx args * Make the mcp server indipendent * User not found returns 200 * Handling for responses with no body * CI performance workflow: fix the deploy step (@W-19170135@) (#3091) * CI perf workflow: fix the deploy step Override the location of the .mobify credential file * More robust fix * Refactor the flags * Add whitespace * Set env vars to dynamic MRT target * Debug invalid key * Pass in cloud origin to env-var API * Pass in cloud origin to e2e-pr workflow * Added tests for update-mrt-target * Remove redundant tests * Remove unused functions * Update agent_checkout_rebase_action.yml Signed-off-by: Nishant Sethunath * Update agent_checkout_rebase_action.yml Signed-off-by: Nishant Sethunath * Fix how 403s are sent now that selfHandleResponse is true * Add test * Remove unused message * Changelog updates * Update translations * Remove test case that is no longer needed * Update agent_checkout_rebase_action.yml Signed-off-by: Nishant Sethunath * Minor Cleanup * CI perf workflow: fix the deploy step.. take two (@W-19345469@) (#3115) * CI perf workflow: fix the deploy step Override the location of the .mobify credential file * More robust fix * Refactor the flags * Add whitespace * Update credentials * Add Slack notification * make actions description clearer * Use overwrite for env vars * Apply feedback * lint * Fix duration issue * Remove changelog from retail app * Modify changelog * Lockdown chakra-ui version to 2.6.0 * Update changelog * Pin v2.7.0 * Bump vendor bundle size * @W-18760799 - E2E tests for SSR logs with B3-Traceid header (#3028) * created e2e tests for SSR logs with B3-Traceid header * reverting a package.json change * Clean up tests * Update CHANGELOG.md * Clean up comments and remove similar test * Update failing test --------- Co-authored-by: Jang ho Jung * A changelog change * Updates supporting SERVER_TIMING env var, fix x-b3 headers sometimes not showing up, error message included in logging, linting * Bring back performanceTimer.log * Skip deleting dwsid on login for if hybrid auth is enabled * Update changelog * Fix whitespace linting * Update implementation and add unit tests * Remove redundant tests * Update test description * Update hbs templates to include hybridAuthEnabled * CI trigger * CI trigger * Update CHANGELOG.md * Refactor update MRT target * Fix incorrect project slug * Fix filename * Remove console.log * Update tests and add samples * FIx linting * Add dotenv to e2e-scripts package.json * @W-19265242@ Configurable base paths for /mobify routes (PWA Kit 3.12 version) (#2892) * Initial restore of base path PR * Rename path functions * Update commerce-sdk-react callbacks and slas private proxy endpoint * Big update * Replace getAppOrigin calls with useAppOrigin * WIP - Filter the base path from all incoming requests to the server * Remove base path from proxied requests + cleanup * Use functions for proxy pathRewrite * A bit of PR cleanup * Lint cleanup * More cleanup * Fix tests * Fix issues found by CI * Lint for override asset * Turn off slas private proxy * Cleanup comments * Update changelog entries * Fix config * Limit base path removal to /mobify or express route * Adjust express filter * Lint * Fix dependency in generated apps * Comment cleanup * use envBasePath as a feature toggle for enabling the base path middleware * Enable basepath removal middlware to handle express routes defined as regexes * Apply suggestions * Adjust app config template * Use pathToRegexp rather than having manual regex interpretation * escape the base path to not allow regex * refactor * Remove ?* from callback url * Handle ? characters in express route * Cache express regexes * Handle properly formatted ()? express groups * Comment cleanup * Apply suggestions to reduce use of regex * Add base path to social login redirectUri * Lint * Update comments and test * Update test with more realistic scenario * Remove path-to-regexp * Refine convertExpressRouteToRegex further * Move express route to regex logic to separate file * Adjust logger and lock file * Revert package-lock changes --------- Signed-off-by: vcua-mobify <47404250+vcua-mobify@users.noreply.github.com> * Link test runs to acquired MRT target * Don't pass cloud origin to create_mrt * Update project slug * Restrict test runs to .spec.js files * Parallelize test runs for e2e and a11y * Parallelize only the test runs * Revert parallel runs * Run playwright tests in parallel * Optimize test run * Optimize playwright browser installs * Optimize test run * Fix linting * Rename job * Clean up test run command * Document parallel runs * Add docs for playwright workers * Update prettier settings * Clean up comments * Fix playwright test command * Fix test run command * Address some feedback * linting * Decrease bundlesize by separating server-side logic * Remove stack traces * Update CHANGELOG.md * Remove unused package * Update CHANGELOG.md * commerce-sdk-react v4 - Remove deprecated properties + fix private client proxy endpoint prop name (#3177) * Clean up useDNT deprecations * Breaking: remove /mobify path constants from commerce-sdk-react * Fix private client prop name * Update changelogs * Update CHANGELOG.md * update more places that called updateDNT * Update a11y snapshots to include unique landmark for toasts * Revert peridot from a11y snapshot * Revert MRT flag * Feat: Consume new OAS Isomorphic SDK (@W-19105545@) (#2879) * refactor * fix build issues * trim uncessarry changes * remove most usages of * fix tests * update test files * fix unit tests * Bump commerce-sdk-isomorphic * Fix tests and lint errors * Bump commerce-sdk-react major version * Revert "Bump commerce-sdk-react major version" This reverts commit a62fcdf14797c1d708833812f7ecde2e97dab0e7. * Bump bundle size * Try with larger runner * Update imports * Update bootstrap config * Update tsconfig * Rename ShopperSeo directory to ShopperSEO & Fix test-commerce-sdk-react * Clean up * Major bump commerce-sdk-react * Create CHANGELOG.md * Remove changelog file from test-commerce-sdk-react * PR Feedback * PR Feedback address TODO * PR Feedback use `helpers.authorizeIDP` instead of manual URL * Use `ShopperLoginTypes.getPasswordResetTokenBodyType` in getPasswordResetToken * Use `ShopperLoginTypes.resetPasswordBodyType` in resetPassword * Use DATA_MAP token_type * Use `loginRegisteredUserB2C` in register() and updateCustomerPassword() * Restore original API interface for `authorizeIDP`, `loginIDPUser` and `authorizePasswordless` * Restore original type names removing Public * Restore `privateClient` parameter in `authorizeIDP` --------- Signed-off-by: Adam Raya Co-authored-by: Adam Raya Navarro Co-authored-by: Adam Raya * Exit workflow if app validation fails * Fix docs * Fix project key * [CI workflow] Send Slack notification to multiple channels (@W-19432819@) (#3182) * Send Slack notification to multiple channels * Add link to the MRT project * Make sure to update all webhook url * Rename job to be more accurate * Debugging * Revert "Debugging" This reverts commit c7f6ef0272e0e0cc5783b4a4242682efda6d20ce. * Inject sfdc_user_agent request header into all SCAPI requests (@W-18404859@) (#3183) * Inject `sfdc_user_agent` request header * Update App Generator pwa-kit-create-app hbs files * Use package name pwa-kit-react-sdk and version in sfdc header * Include headers in commerce-sdk-react auth request * lint * Rename utils file * lint * Fix import path in generated project * Update Changelog.md * PR Feedback * PR Feedback * Bump max bundle size (#3191) * @W-18891305 MULTISHIP merge to develop (#3056) * Apply more feedback * linting * more linting * docs: Add missing period to JSDoc comment in page-designer utils * Pin package version for @testing-library/user-event * Set retail-react-app-test-project to be non-extensible * Update changelog * Update changelog * Generate project outside the monorepo * @W-19442561 - Fix Incorrectly Disabled Continue to Shipping Method Button for Multiship (#3199) * W-19330454 - Release PWA Kit 3.12.0-preview.2 (#3193) * bump versions and update changelogs * Add breaking label to multi-ship changelog entry * snyc to -preview.2 * Comment out problematic job * Update e2e-pr.yml * @W-19443375,@W-19350149 MULTISHIP fix address function mismatch and options promotions (#3203) * @W-19451314 - Add ShipmentId to Create Shipment Body (#3211) * Update CWD paths for generated tests * CHeck test files * [`develop` branch] Bump up the dev versions (#3208) * Bump up dev versions The previous dev versions have been "claimed", since they were accidentally published to npm. We need to use unclaimed dev versions for our local generator (create-mobify-app-dev.js) to work properly. * This e2e package is meant to be private * Update CHANGELOG.md * Update package-lock.json * Enable e2e-PR workflow * Check retail app home * Log retail_app_home * Print MRT target json * skip browser installs * Fix retail app home * Move setting retail app home to run tests step * Remove node_modules from checks * Set retail app home to process.env not GITHUB_ENV * Enable tests * Remove logging the json * Uncomment the test run * Temp reduce retries * skip telemetry and a11y tests * Add fallback to validate script * Rewrite script to propagate errors * Fix guest shopper checkout * Fix linting * Update labels on checkout page * Restore retry * Move snapshots to slas client specific dir * Split a11y snapshots for public/private clients * Update e2e workflow a11y command * Fix button label for payment info * Fix validation script falirue * Update package lock files * Make sure that the generated-projects folder is created * @W-19349040 MULTISHIP fix form default delivery option selection (#3217) * Further refactoring * Fix count_deps action so it knows where the generated project is * Looks like on windows, we can run bash shell with front slashes * @ W-19450629 feat: remove quantity selector from shipping methods preview cart in multi-ship (#3216) * remove quantity selector from shipping methods preview cart in multi ship * update change log --------- Signed-off-by: sf-henry-semaganda * Fix non-extensible template to use correct passwordless login callback uri (#3222) * Debug validation * Debug validation * Debug validation * Remove console.logs * Flip generator response options * Update hover link test * skip failing test * Lint fix * Document test skip * Remove process.exit * Throw err * Remove debug step * Check if monorepo is a dev version before publishing to npm (#3210) * Check if monorepo is a dev version before publishing to npm * Check env.IS_DEV_VERSION * test step * test * Apply suggestion * More test * test * test * test * use if block * test * test * use quiet mode * test * test * test * test * test * test * test * Try this regex that's perhaps more compatible * Re-enable other steps and clean up verification step --------- Co-authored-by: Vincent Marta * Fix usage for Object.hasOwn * Fix linting * update the mcp tool name and readme * Fix: Don't pass `code_challenge` if its undefined in `getPasswordResetToken` (#3228) * add fix * make code challenge optional * lint --------- Co-authored-by: vcua-mobify <47404250+vcua-mobify@users.noreply.github.com> * resize the images * update CHANGELOG.md * @W-19364221 - Handle Removal of Last Address for Registered (#3231) * @W-19327219: Update in isolation tests in MRT reference app (#3172) * updated isolation API * updated isolation testcases * Cleanup of tool * Added tests * updated tool * Gracefully handle when some config.app.* properties are missing (@W-19453183@) (#3230) * Don't assume that config.app.* properties exist * Don't assume that config.app.* properties exist * Add todos * Use the default config for Commerce Agent when necessary * Debugging * Move utils file to somewhere more upgradable and extensible * No longer need these .hbs files * Sync the corresponding .hbs files * Fix import path * Fix require path so it can work inside monorepo * Refactor to make it easier to get commerce agent config * Fix module error Turns out that I can't mix both esm and commonjs ways. * Sync the eslint comments * Remove commented out code * Update CHANGELOG.md --------- Signed-off-by: Vincent Marta Co-authored-by: vcua-mobify <47404250+vcua-mobify@users.noreply.github.com> * @W-19364130 feat: fix shipping costs summary mode pricing (#3240) * fix shipping costs sumary mode * update change log * add clear annotation * add test coverage * update basket too * update * Handle OS specific filepaths in tests * @W-19330454@ - Release PWA Kit 3.12.0-preview.3 (#3229) * bump versions and update changelogs * Add breaking label to multi-ship changelog entry * snyc to -preview.2 * @W-19442561 - Fix Incorrectly Disabled Continue to Shipping Method Button for Multiship (#3199) * @W-19443375,@W-19350149 MULTISHIP fix address function mismatch and options promotions (#3203) * @W-19451314 - Add ShipmentId to Create Shipment Body (#3211) * @W-19349040 MULTISHIP fix form default delivery option selection (#3217) * @ W-19450629 feat: remove quantity selector from shipping methods preview cart in multi-ship (#3216) * remove quantity selector from shipping methods preview cart in multi ship * update change log --------- Signed-off-by: sf-henry-semaganda * Fix non-extensible template to use correct passwordless login callback uri (#3222) * Check if monorepo is a dev version before publishing to npm (#3210) * Check if monorepo is a dev version before publishing to npm * Check env.IS_DEV_VERSION * test step * test * Apply suggestion * More test * test * test * test * use if block * test * test * use quiet mode * test * test * test * test * test * test * test * Try this regex that's perhaps more compatible * Re-enable other steps and clean up verification step --------- Co-authored-by: Vincent Marta * Merge pull request #3204 from SalesforceCommerceCloud/bugfix/pin-testing-library-version @W-19443536 - [Bugfix] Fix unit test failures in generated projects * Fix: Don't pass `code_challenge` if its undefined in `getPasswordResetToken` (#3228) * add fix * make code challenge optional * lint --------- Co-authored-by: vcua-mobify <47404250+vcua-mobify@users.noreply.github.com> * @W-19364221 - Handle Removal of Last Address for Registered (#3231) * Gracefully handle when some config.app.* properties are missing (@W-19453183@) (#3230) * Don't assume that config.app.* properties exist * Don't assume that config.app.* properties exist * Add todos * Use the default config for Commerce Agent when necessary * Debugging * Move utils file to somewhere more upgradable and extensible * No longer need these .hbs files * Sync the corresponding .hbs files * Fix import path * Fix require path so it can work inside monorepo * Refactor to make it easier to get commerce agent config * Fix module error Turns out that I can't mix both esm and commonjs ways. * Sync the eslint comments * Remove commented out code * Update CHANGELOG.md --------- Signed-off-by: Vincent Marta Co-authored-by: vcua-mobify <47404250+vcua-mobify@users.noreply.github.com> * @W-19364130 feat: fix shipping costs summary mode pricing (#3240) * fix shipping costs sumary mode * update change log * add clear annotation * add test coverage * update basket too * update * bump versions to -preview.3 * Merge pull request #3214 from SalesforceCommerceCloud/restore-per-pr-e2e Restore e2e-PR workflow * Fix e2e-pr after incorrect merge resolution * preview.3 not preview-3 --------- Signed-off-by: sf-henry-semaganda Signed-off-by: Vincent Marta Co-authored-by: shauryemahajanSF <143645277+shauryemahajanSF@users.noreply.github.com> Co-authored-by: patricksullivansf <100441292+patricksullivansf@users.noreply.github.com> Co-authored-by: sf-henry-semaganda Co-authored-by: Vincent Marta Co-authored-by: Jainam Sheth <99490559+shethj@users.noreply.github.com> Co-authored-by: Joel Uong <88680517+joeluong-sfcc@users.noreply.github.com> * Enable OTEL test * Bump up to final/stable versions * Update package-lock.json * Update changelog files * Sync the mcp package with develop branch * Remove this warning message (#3244) It's not as helpful because it can confuse other people and cause false alarm. One use of this function is to parse an optional environment variable. So if it is not set, it'll constantly prints out the warning message. * Bump up to the next dev versions * Update changelog files * @W-19560496 merge develop to search feature * @W-19560496 merge develop to search feature * @W-19560496 merge develop to search feature --------- Signed-off-by: Nishant Sethunath Signed-off-by: vcua-mobify <47404250+vcua-mobify@users.noreply.github.com> Signed-off-by: Jainam Sheth <99490559+shethj@users.noreply.github.com> Signed-off-by: jeremy-jung1 <140001271+jeremy-jung1@users.noreply.github.com> Signed-off-by: Adam Raya Signed-off-by: sf-henry-semaganda Signed-off-by: Vincent Marta Co-authored-by: vcua-mobify Co-authored-by: Jainam Sheth Co-authored-by: Jang ho Jung Co-authored-by: Ben Chypak Co-authored-by: Ben Chypak Co-authored-by: Vincent Marta Co-authored-by: Nishant Sethunath Co-authored-by: snilakandan Co-authored-by: snilakandan13 <119348013+snilakandan13@users.noreply.github.com> Co-authored-by: Jainam Sheth <99490559+shethj@users.noreply.github.com> Co-authored-by: jeremy-jung1 <140001271+jeremy-jung1@users.noreply.github.com> Co-authored-by: Larnelle Ankunda Co-authored-by: vcua-mobify <47404250+vcua-mobify@users.noreply.github.com> Co-authored-by: Joel Uong <88680517+joeluong-sfcc@users.noreply.github.com> Co-authored-by: Adam Raya Navarro Co-authored-by: Adam Raya Co-authored-by: shauryemahajanSF <143645277+shauryemahajanSF@users.noreply.github.com> Co-authored-by: patricksullivansf <100441292+patricksullivansf@users.noreply.github.com> Co-authored-by: sf-xingquan-jin Co-authored-by: sf-henry-semaganda Co-authored-by: wei-liu Co-authored-by: Rahul Kumawat * @W-19593698 Search enhancements * @W-19593698 Search enhancements * @W-19593698 Search enhancements * @W-19593698 Search enhancements * @W-19593698 Search enhancements * @W-19593698 Search enhancements address pr comments * W-19396339 fix e2e test --------- Signed-off-by: Ben Chypak Signed-off-by: Jainam Sheth <99490559+shethj@users.noreply.github.com> Signed-off-by: Nishant Sethunath Signed-off-by: vcua-mobify <47404250+vcua-mobify@users.noreply.github.com> Signed-off-by: jeremy-jung1 <140001271+jeremy-jung1@users.noreply.github.com> Signed-off-by: Adam Raya Signed-off-by: sf-henry-semaganda Signed-off-by: Vincent Marta Co-authored-by: vcua-mobify Co-authored-by: Ben Chypak Co-authored-by: Ben Chypak Co-authored-by: Shailesh Zirpe Co-authored-by: Jainam Sheth Co-authored-by: Jainam Sheth <99490559+shethj@users.noreply.github.com> Co-authored-by: vcua-mobify <47404250+vcua-mobify@users.noreply.github.com> Co-authored-by: Alex Vuong Co-authored-by: Shailesh Zirpe <42553862+szirpesf@users.noreply.github.com> Co-authored-by: wei-liu Co-authored-by: atekur Co-authored-by: Aditya Tekur <126732792+aditek-sf@users.noreply.github.com> Co-authored-by: Ujwala <59447810+unandyala@users.noreply.github.com> Co-authored-by: Avinash Kasipathy Co-authored-by: snilakandan Co-authored-by: Vincent Marta Co-authored-by: snilakandan13 <119348013+snilakandan13@users.noreply.github.com> Co-authored-by: Katia Hage Co-authored-by: patricksullivansf <100441292+patricksullivansf@users.noreply.github.com> Co-authored-by: Yuming Hsieh Co-authored-by: Brian Feister <47546998+bfeister@users.noreply.github.com> Co-authored-by: jitendra.bothra Co-authored-by: jbothra-sf <152039392+jbothra-sf@users.noreply.github.com> Co-authored-by: Nishant Sethunath Co-authored-by: Adam Raya Co-authored-by: Jang ho Jung Co-authored-by: jeremy-jung1 <140001271+jeremy-jung1@users.noreply.github.com> Co-authored-by: Larnelle Ankunda Co-authored-by: Joel Uong <88680517+joeluong-sfcc@users.noreply.github.com> Co-authored-by: Adam Raya Navarro Co-authored-by: shauryemahajanSF <143645277+shauryemahajanSF@users.noreply.github.com> Co-authored-by: sf-xingquan-jin Co-authored-by: sf-henry-semaganda Co-authored-by: Rahul Kumawat --- e2e/scripts/pageHelpers.js | 1 + .../template-retail-react-app/CHANGELOG.md | 1 + .../app/components/search/index.jsx | 48 ++++-- .../app/components/search/index.test.js | 101 +++++++++++- .../partials/horizontal-suggestions.jsx | 68 ++++++++ .../partials/horizontal-suggestions.test.jsx | 110 +++++++++++++ .../partials/search-suggestions-section.jsx | 145 ++++++++++++++++++ .../search-suggestions-section.test.jsx | 139 +++++++++++++++++ .../search/partials/search-suggestions.jsx | 33 ++-- .../search/partials/suggestions.jsx | 40 +++-- .../static/translations/compiled/en-GB.json | 24 +++ .../static/translations/compiled/en-US.json | 24 +++ .../static/translations/compiled/en-XA.json | 56 +++++++ .../project/horizontal-suggestions.js | 59 +++++++ .../components/project/search-suggestions.js | 82 ++++++++++ .../app/theme/index.js | 6 +- .../template-retail-react-app/package.json | 2 +- .../translations/en-GB.json | 12 ++ .../translations/en-US.json | 12 ++ 19 files changed, 918 insertions(+), 45 deletions(-) create mode 100644 packages/template-retail-react-app/app/components/search/partials/horizontal-suggestions.jsx create mode 100644 packages/template-retail-react-app/app/components/search/partials/horizontal-suggestions.test.jsx create mode 100644 packages/template-retail-react-app/app/components/search/partials/search-suggestions-section.jsx create mode 100644 packages/template-retail-react-app/app/components/search/partials/search-suggestions-section.test.jsx create mode 100644 packages/template-retail-react-app/app/theme/components/project/horizontal-suggestions.js create mode 100644 packages/template-retail-react-app/app/theme/components/project/search-suggestions.js diff --git a/e2e/scripts/pageHelpers.js b/e2e/scripts/pageHelpers.js index 86c3fc51ff..1269e427e0 100644 --- a/e2e/scripts/pageHelpers.js +++ b/e2e/scripts/pageHelpers.js @@ -439,6 +439,7 @@ export const searchProduct = async ({page, query, isMobile = false}) => { let searchInput = isMobile ? searchInputs.nth(1) : searchInputs.nth(0) await searchInput.fill(query) + await page.waitForTimeout(1000) await searchInput.press('Enter') await page.waitForLoadState() diff --git a/packages/template-retail-react-app/CHANGELOG.md b/packages/template-retail-react-app/CHANGELOG.md index 87d48cbc16..f0204c6d94 100644 --- a/packages/template-retail-react-app/CHANGELOG.md +++ b/packages/template-retail-react-app/CHANGELOG.md @@ -1,4 +1,5 @@ ## v8.1.0-dev (Sep 04, 2025) +- Updated search UX - prices, images, suggestions new layout [#3271](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3271) - Updated the UI for StoreDisplay component which displays pickup in-store information on different pages. [#3248](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3248) - Added warning modal for guest users when toggling between multi ship and ship to one address. [3280] (https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3280) [3302] (https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3302) diff --git a/packages/template-retail-react-app/app/components/search/index.jsx b/packages/template-retail-react-app/app/components/search/index.jsx index bdd310ddfb..3257abd7f1 100644 --- a/packages/template-retail-react-app/app/components/search/index.jsx +++ b/packages/template-retail-react-app/app/components/search/index.jsx @@ -23,7 +23,6 @@ import SearchSuggestions from '@salesforce/retail-react-app/app/components/searc import {SearchIcon} from '@salesforce/retail-react-app/app/components/icons' import { capitalize, - boldString, getSessionJSONItem, setSessionJSONItem } from '@salesforce/retail-react-app/app/utils/utils' @@ -50,7 +49,7 @@ function isAskAgentOnSearchEnabled(enabled, askAgentOnSearch) { return enabled === 'true' && askAgentOnSearch === 'true' && onClient } -const formatSuggestions = (searchSuggestions, input) => { +const formatSuggestions = (searchSuggestions) => { return { categorySuggestions: searchSuggestions?.categorySuggestions?.categories?.map( (suggestion) => { @@ -58,7 +57,9 @@ const formatSuggestions = (searchSuggestions, input) => { type: 'category', id: suggestion.id, link: categoryUrlBuilder({id: suggestion.id}), - name: boldString(suggestion.name, capitalize(input)) + name: capitalize(suggestion.name), + image: suggestion.image?.disBaseLink, + parentCategoryName: suggestion.parentCategoryName } } ), @@ -68,19 +69,30 @@ const formatSuggestions = (searchSuggestions, input) => { currency: product.currency, price: product.price, productId: product.productId, - name: boldString(product.productName, capitalize(input)), - link: productUrlBuilder({id: product.productId}) + name: capitalize(product.productName), + link: productUrlBuilder({id: product.productId}), + image: product.image?.disBaseLink } }), - phraseSuggestions: searchSuggestions?.categorySuggestions?.suggestedPhrases?.map( + brandSuggestions: searchSuggestions?.brandSuggestions?.suggestedPhrases?.map((brand) => { + // Init cap the brand name + return { + type: 'brand', + name: capitalize(brand.phrase), + link: searchUrlBuilder(brand.phrase) + } + }), + phraseSuggestions: searchSuggestions?.productSuggestions?.suggestedPhrases?.map( (phrase) => { return { type: 'phrase', - name: boldString(phrase.phrase, capitalize(input)), - link: searchUrlBuilder(phrase.phrase) + name: phrase.phrase, + link: searchUrlBuilder(phrase.phrase), + exactMatch: phrase.exactMatch } } - ) + ), + searchPhrase: searchSuggestions?.searchPhrase } } @@ -102,10 +114,12 @@ const Search = (props) => { const [isOpen, setIsOpen] = useState(false) const [searchQuery, setSearchQuery] = useState('') const navigate = useNavigation() + const searchSuggestion = useSearchSuggestions( { parameters: { - q: searchQuery + q: searchQuery, + expand: 'images,prices' } }, { @@ -119,7 +133,7 @@ const Search = (props) => { }) const recentSearches = getSessionJSONItem(RECENT_SEARCH_KEY) const searchSuggestions = useMemo( - () => formatSuggestions(searchSuggestion.data, searchInputRef?.current?.value), + () => formatSuggestions(searchSuggestion.data), [searchSuggestion] ) @@ -257,7 +271,7 @@ const Search = (props) => { // or we have search suggestions available and have inputed some text (empty text in this scenario should show recent searches) if ( (document.activeElement.id === 'search-input' && recentSearches?.length > 0) || - (searchSuggestionsAvailable && searchInputRef.current.value.length > 0) + (searchSuggestionsAvailable && searchInputRef.current?.value?.length > 0) ) { setIsOpen(true) } else { @@ -310,7 +324,15 @@ const Search = (props) => { - + { const suggestionPopoverEl = await screen.getByTestId('sf-suggestion-popover') await waitFor(() => { - const suggestionsEl = within(suggestionPopoverEl).getByTestId('sf-suggestion') - expect(suggestionsEl.querySelector('button').textContent).toBe('Dresses') + const suggestionsEls = within(suggestionPopoverEl).getAllByTestId('sf-suggestion') + expect(suggestionsEls.length).toBeGreaterThan(0) + const hasDressesSuggestion = suggestionsEls.some((el) => + el.querySelector('button')?.textContent?.includes('Dresses') + ) + expect(hasDressesSuggestion).toBe(true) }) }) @@ -423,3 +427,96 @@ test('when sendTextMessage and launchChat both fail, no additional send text is // Verify sendTextMessage was only called once expect(sendTextMessageSpy).toHaveBeenCalledTimes(1) }) + +test('handles search phrase in formatSuggestions', async () => { + const user = setupUserEvent() + + const mockResultsWithPhrase = { + ...mockSearchResults, + searchPhrase: 'test search phrase' + } + + global.server.use( + rest.get('*/search-suggestions', (req, res, ctx) => { + return res(ctx.delay(0), ctx.status(200), ctx.json(mockResultsWithPhrase)) + }) + ) + + renderWithProviders() + const searchInput = document.querySelector('input[type="search"]') + + await user.type(searchInput, 'test') + + // Wait for suggestions to load with search phrase + await waitFor(() => { + expect(screen.getByTestId('sf-suggestion-popover')).toBeInTheDocument() + }) +}) + +test('handles phrase suggestions in formatSuggestions', async () => { + const user = setupUserEvent() + + // Mock search results with phrase suggestions + const mockResultsWithPhrases = { + ...mockSearchResults, + productSuggestions: { + ...mockSearchResults.productSuggestions, + suggestedPhrases: [ + {phrase: 'running shoes', exactMatch: true}, + {phrase: 'athletic wear', exactMatch: false} + ] + } + } + + global.server.use( + rest.get('*/search-suggestions', (req, res, ctx) => { + return res(ctx.delay(0), ctx.status(200), ctx.json(mockResultsWithPhrases)) + }) + ) + + renderWithProviders() + const searchInput = document.querySelector('input[type="search"]') + + await user.type(searchInput, 'running') + + // Wait for suggestions to load with phrase suggestions + await waitFor(() => { + expect(screen.getByTestId('sf-suggestion-popover')).toBeInTheDocument() + }) +}) + +test('handles product suggestions with images', async () => { + const user = setupUserEvent() + + // Mock search results with product suggestions that have images + const mockResultsWithProductImages = { + ...mockSearchResults, + productSuggestions: { + ...mockSearchResults.productSuggestions, + products: [ + { + ...mockSearchResults.productSuggestions.products[0], + image: { + disBaseLink: 'https://example.com/product-image.jpg' + } + } + ] + } + } + + global.server.use( + rest.get('*/search-suggestions', (req, res, ctx) => { + return res(ctx.delay(0), ctx.status(200), ctx.json(mockResultsWithProductImages)) + }) + ) + + renderWithProviders() + const searchInput = document.querySelector('input[type="search"]') + + await user.type(searchInput, 'Dress') + + // Wait for suggestions to load with product images + await waitFor(() => { + expect(screen.getByTestId('sf-suggestion-popover')).toBeInTheDocument() + }) +}) diff --git a/packages/template-retail-react-app/app/components/search/partials/horizontal-suggestions.jsx b/packages/template-retail-react-app/app/components/search/partials/horizontal-suggestions.jsx new file mode 100644 index 0000000000..4e693394ec --- /dev/null +++ b/packages/template-retail-react-app/app/components/search/partials/horizontal-suggestions.jsx @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2021, salesforce.com, inc. + * All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +import React from 'react' +import PropTypes from 'prop-types' +import {Text, Box, Flex, AspectRatio} from '@salesforce/retail-react-app/app/components/shared/ui' +import DynamicImage from '@salesforce/retail-react-app/app/components/dynamic-image' +import Link from '@salesforce/retail-react-app/app/components/link' +import {useStyleConfig} from '@chakra-ui/react' + +const HorizontalSuggestions = ({suggestions, closeAndNavigate, dynamicImageProps}) => { + const styles = useStyleConfig('HorizontalSuggestions') + + if (!suggestions) { + return null + } + + return ( + + + {suggestions.map((suggestion, idx) => ( + closeAndNavigate(suggestion.link)} + > + + {/* Product Image */} + + {suggestion.image ? ( + + + + ) : null} + + + {suggestion.name} + + {suggestion.price && ( + ${suggestion.price} + )} + + + ))} + + + ) +} + +HorizontalSuggestions.propTypes = { + suggestions: PropTypes.array, + closeAndNavigate: PropTypes.func, + dynamicImageProps: PropTypes.object +} + +export default HorizontalSuggestions diff --git a/packages/template-retail-react-app/app/components/search/partials/horizontal-suggestions.test.jsx b/packages/template-retail-react-app/app/components/search/partials/horizontal-suggestions.test.jsx new file mode 100644 index 0000000000..77750aa5db --- /dev/null +++ b/packages/template-retail-react-app/app/components/search/partials/horizontal-suggestions.test.jsx @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2021, salesforce.com, inc. + * All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +import React from 'react' +import {screen} from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import {renderWithProviders} from '@salesforce/retail-react-app/app/utils/test-utils' +import HorizontalSuggestions from '@salesforce/retail-react-app/app/components/search/partials/horizontal-suggestions' + +jest.mock('@salesforce/retail-react-app/app/components/dynamic-image', () => { + return function MockDynamicImage(props) { + const {src, widths, imageProps} = props || {} + return ( + {imageProps?.alt + ) + } +}) + +const sampleSuggestions = [ + { + link: '/product-1', + image: 'https://example.com/image-1.jpg', + name: 'Product 1', + price: '29.99' + }, + { + link: '/product-2', + name: 'Product 2' + } +] + +test('returns null when suggestions are undefined', () => { + renderWithProviders( + + ) + expect(screen.queryByTestId('sf-horizontal-product-suggestions')).not.toBeInTheDocument() +}) + +test('renders product tiles with names and optional prices', () => { + renderWithProviders( + + ) + + // container + expect(screen.getByTestId('sf-horizontal-product-suggestions')).toBeInTheDocument() + + // tiles + const tiles = screen.getAllByTestId('product-tile') + expect(tiles).toHaveLength(2) + + // names + expect(screen.getByText('Product 1')).toBeInTheDocument() + expect(screen.getByText('Product 2')).toBeInTheDocument() + + // price only for first suggestion + expect(screen.getByText('$29.99')).toBeInTheDocument() +}) + +test('renders DynamicImage when image is provided and passes widths via dynamicImageProps', () => { + const dynamicImageProps = {widths: [200, 400, 800]} + renderWithProviders( + + ) + + const images = screen.getAllByTestId('dynamic-image') + // Only first suggestion has an image + expect(images).toHaveLength(1) + + const img = images[0] + // src should be appended with the width token by the component + expect(img.getAttribute('data-src')).toBe(`${sampleSuggestions[0].image}[?sw={width}&q=60]`) + expect(img.getAttribute('data-widths')).toBe('200,400,800') +}) + +test('does not render DynamicImage when image is absent', () => { + renderWithProviders( + + ) + + expect(screen.queryByTestId('dynamic-image')).not.toBeInTheDocument() +}) + +test('clicking a product tile triggers closeAndNavigate with the link', async () => { + const user = userEvent.setup() + const closeAndNavigate = jest.fn() + + renderWithProviders( + + ) + + const tiles = screen.getAllByTestId('product-tile') + await user.click(tiles[1]) + + expect(closeAndNavigate).toHaveBeenCalledWith(sampleSuggestions[1].link) +}) diff --git a/packages/template-retail-react-app/app/components/search/partials/search-suggestions-section.jsx b/packages/template-retail-react-app/app/components/search/partials/search-suggestions-section.jsx new file mode 100644 index 0000000000..2723f7bf52 --- /dev/null +++ b/packages/template-retail-react-app/app/components/search/partials/search-suggestions-section.jsx @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2021, salesforce.com, inc. + * All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +import React, {Fragment} from 'react' +import PropTypes from 'prop-types' +import {Box} from '@salesforce/retail-react-app/app/components/shared/ui' +import Suggestions from '@salesforce/retail-react-app/app/components/search/partials/suggestions' +import HorizontalSuggestions from '@salesforce/retail-react-app/app/components/search/partials/horizontal-suggestions' +import {FormattedMessage} from 'react-intl' +import {HideOnDesktop, HideOnMobile} from '@salesforce/retail-react-app/app/components/responsive' +import Link from '@salesforce/retail-react-app/app/components/link' +import {searchUrlBuilder} from '@salesforce/retail-react-app/app/utils/url' + +const SuggestionSection = ({searchSuggestions, closeAndNavigate, styles}) => { + const hasCategories = searchSuggestions?.categorySuggestions?.length + const hasProducts = searchSuggestions?.productSuggestions?.length + const hasPhraseSuggestions = searchSuggestions?.phraseSuggestions?.length + + return ( + + {/* Mobile - Vertical alignment */} + + {hasPhraseSuggestions && + searchSuggestions?.phraseSuggestions[0].exactMatch === false && ( + + + + + {' ' + searchSuggestions?.phraseSuggestions[0].name + '?'} + + + + )} + {hasCategories && ( + + + + + + + )} + {hasProducts && ( + + + + + + + )} + + {/* Desktop - Vertical and Horizontal alignment */} + + + + {hasPhraseSuggestions && + searchSuggestions?.phraseSuggestions[0].exactMatch === false && ( + + + + + {' ' + + searchSuggestions?.phraseSuggestions[0].name + + '?'} + + + + )} + {hasCategories && ( + + + + + + + )} + + + {hasProducts && ( + + + + )} + + + {hasProducts && ( + + + + + + + + )} + + + + + ) +} + +SuggestionSection.propTypes = { + searchSuggestions: PropTypes.object.isRequired, + closeAndNavigate: PropTypes.func.isRequired, + styles: PropTypes.object.isRequired +} + +export default SuggestionSection diff --git a/packages/template-retail-react-app/app/components/search/partials/search-suggestions-section.test.jsx b/packages/template-retail-react-app/app/components/search/partials/search-suggestions-section.test.jsx new file mode 100644 index 0000000000..3f0a3a5933 --- /dev/null +++ b/packages/template-retail-react-app/app/components/search/partials/search-suggestions-section.test.jsx @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2021, salesforce.com, inc. + * All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +import React from 'react' +import {screen, within} from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import {renderWithProviders} from '@salesforce/retail-react-app/app/utils/test-utils' +import SuggestionSection from '@salesforce/retail-react-app/app/components/search/partials/search-suggestions-section' + +// Mock dynamic image to keep DOM simple when HorizontalSuggestions renders +jest.mock('@salesforce/retail-react-app/app/components/dynamic-image', () => { + return function MockDynamicImage(props) { + const {src, widths, imageProps} = props || {} + return ( + {imageProps?.alt + ) + } +}) + +const baseStyles = { + textContainer: {}, + sectionHeader: {}, + phraseContainer: {} +} + +const makeSearchSuggestions = (overrides = {}) => ({ + searchPhrase: 'Dress', + phraseSuggestions: [], + categorySuggestions: [], + productSuggestions: [], + ...overrides +}) + +test('renders "Did you mean" with suggestion link when non-exact phrase exists (mobile and desktop)', () => { + const searchSuggestions = makeSearchSuggestions({ + phraseSuggestions: [{name: 'dresses', link: '/search?q=dresses', exactMatch: false}] + }) + + renderWithProviders( + + ) + + // Appears in both mobile and desktop sections + const didYouMeanTexts = screen.getAllByText(/Did you mean/i) + expect(didYouMeanTexts.length).toBeGreaterThanOrEqual(1) + + const links = screen.getAllByRole('link', {name: /dresses\?/i}) + expect(links.length).toBeGreaterThanOrEqual(1) +}) + +test('renders Categories header and category suggestions', () => { + const searchSuggestions = makeSearchSuggestions({ + categorySuggestions: [ + {type: 'category', name: 'Women', link: '/women'}, + {type: 'category', name: 'Men', link: '/men'} + ] + }) + + renderWithProviders( + + ) + + // Header present (could be duplicated for mobile/desktop) + expect(screen.getAllByText('Categories').length).toBeGreaterThanOrEqual(1) + + // Suggestions component renders buttons; ensure names are present + expect(screen.getAllByText('Women').length).toBeGreaterThanOrEqual(1) + expect(screen.getAllByText('Men').length).toBeGreaterThanOrEqual(1) +}) + +test('renders horizontal product suggestions and "View All"; clicking a tile calls closeAndNavigate', async () => { + const user = userEvent.setup() + const closeAndNavigate = jest.fn() + + const searchSuggestions = makeSearchSuggestions({ + productSuggestions: [ + { + type: 'product', + name: 'Product 1', + link: '/p1', + image: 'https://example.com/p1.jpg', + price: '19.99' + }, + {type: 'product', name: 'Product 2', link: '/p2'} + ] + }) + + renderWithProviders( + + ) + + // HorizontalSuggestions container + expect(screen.getByTestId('sf-horizontal-product-suggestions')).toBeInTheDocument() + + // "View All" link only renders when products exist (may be hidden by responsive wrapper in tests) + expect(screen.getByText(/View All/i, {selector: 'a'})).toBeInTheDocument() + + // Click a product tile (desktop horizontal suggestions) + const container = screen.getByTestId('sf-horizontal-product-suggestions') + const tiles = within(container).getAllByTestId('product-tile') + await user.click(tiles[1]) + expect(closeAndNavigate).toHaveBeenCalledWith('/p2') +}) + +test('renders nothing when there are no categories, products, or phrase suggestions', () => { + const searchSuggestions = makeSearchSuggestions() + + renderWithProviders( + + ) + + expect(screen.queryByText('Categories')).not.toBeInTheDocument() + expect(screen.queryByText('Products')).not.toBeInTheDocument() + expect(screen.queryByTestId('sf-horizontal-product-suggestions')).not.toBeInTheDocument() +}) diff --git a/packages/template-retail-react-app/app/components/search/partials/search-suggestions.jsx b/packages/template-retail-react-app/app/components/search/partials/search-suggestions.jsx index 29c5c14067..c09ca93abe 100644 --- a/packages/template-retail-react-app/app/components/search/partials/search-suggestions.jsx +++ b/packages/template-retail-react-app/app/components/search/partials/search-suggestions.jsx @@ -4,28 +4,27 @@ * SPDX-License-Identifier: BSD-3-Clause * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import React, {Fragment} from 'react' +import React from 'react' import PropTypes from 'prop-types' -import {Stack} from '@salesforce/retail-react-app/app/components/shared/ui' +import {Stack, useMultiStyleConfig} from '@salesforce/retail-react-app/app/components/shared/ui' import RecentSearches from '@salesforce/retail-react-app/app/components/search/partials/recent-searches' -import Suggestions from '@salesforce/retail-react-app/app/components/search/partials/suggestions' +import SuggestionSection from '@salesforce/retail-react-app/app/components/search/partials/search-suggestions-section' const SearchSuggestions = ({recentSearches, searchSuggestions, closeAndNavigate}) => { - const useSuggestions = searchSuggestions && searchSuggestions?.categorySuggestions?.length + const styles = useMultiStyleConfig('SearchSuggestions') + const hasCategories = searchSuggestions?.categorySuggestions?.length + const hasProducts = searchSuggestions?.productSuggestions?.length + const hasBrands = searchSuggestions?.brandSuggestions?.length + const hasSuggestions = hasCategories || hasProducts || hasBrands + return ( - - {useSuggestions ? ( - - - {/* */} - {/* */} - + + {hasSuggestions ? ( + ) : ( { + const styles = useMultiStyleConfig('SearchSuggestions') + if (!suggestions) { return null } return ( - - + + {suggestions.map((suggestion, idx) => ( ))} diff --git a/packages/template-retail-react-app/app/static/translations/compiled/en-GB.json b/packages/template-retail-react-app/app/static/translations/compiled/en-GB.json index 0066215b23..c40aaaca4c 100644 --- a/packages/template-retail-react-app/app/static/translations/compiled/en-GB.json +++ b/packages/template-retail-react-app/app/static/translations/compiled/en-GB.json @@ -3419,6 +3419,30 @@ "value": "Cancel" } ], + "search.suggestions.categories": [ + { + "type": 0, + "value": "Categories" + } + ], + "search.suggestions.didYouMean": [ + { + "type": 0, + "value": "Did you mean" + } + ], + "search.suggestions.products": [ + { + "type": 0, + "value": "Products" + } + ], + "search.suggestions.viewAll": [ + { + "type": 0, + "value": "View All" + } + ], "selected_refinements.action.assistive_msg.clear_all": [ { "type": 0, diff --git a/packages/template-retail-react-app/app/static/translations/compiled/en-US.json b/packages/template-retail-react-app/app/static/translations/compiled/en-US.json index 0066215b23..c40aaaca4c 100644 --- a/packages/template-retail-react-app/app/static/translations/compiled/en-US.json +++ b/packages/template-retail-react-app/app/static/translations/compiled/en-US.json @@ -3419,6 +3419,30 @@ "value": "Cancel" } ], + "search.suggestions.categories": [ + { + "type": 0, + "value": "Categories" + } + ], + "search.suggestions.didYouMean": [ + { + "type": 0, + "value": "Did you mean" + } + ], + "search.suggestions.products": [ + { + "type": 0, + "value": "Products" + } + ], + "search.suggestions.viewAll": [ + { + "type": 0, + "value": "View All" + } + ], "selected_refinements.action.assistive_msg.clear_all": [ { "type": 0, diff --git a/packages/template-retail-react-app/app/static/translations/compiled/en-XA.json b/packages/template-retail-react-app/app/static/translations/compiled/en-XA.json index ee8e0a6ef5..fab642170b 100644 --- a/packages/template-retail-react-app/app/static/translations/compiled/en-XA.json +++ b/packages/template-retail-react-app/app/static/translations/compiled/en-XA.json @@ -7219,6 +7219,62 @@ "value": "]" } ], + "search.suggestions.categories": [ + { + "type": 0, + "value": "[" + }, + { + "type": 0, + "value": "Ƈȧȧŧḗḗɠǿǿřīḗḗş" + }, + { + "type": 0, + "value": "]" + } + ], + "search.suggestions.didYouMean": [ + { + "type": 0, + "value": "[" + }, + { + "type": 0, + "value": "Ḓīḓ ẏǿǿŭŭ ḿḗḗȧȧƞ" + }, + { + "type": 0, + "value": "]" + } + ], + "search.suggestions.products": [ + { + "type": 0, + "value": "[" + }, + { + "type": 0, + "value": "Ƥřǿǿḓŭŭƈŧş" + }, + { + "type": 0, + "value": "]" + } + ], + "search.suggestions.viewAll": [ + { + "type": 0, + "value": "[" + }, + { + "type": 0, + "value": "Ṽīḗḗẇ Ȧŀŀ" + }, + { + "type": 0, + "value": "]" + } + ], "selected_refinements.action.assistive_msg.clear_all": [ { "type": 0, diff --git a/packages/template-retail-react-app/app/theme/components/project/horizontal-suggestions.js b/packages/template-retail-react-app/app/theme/components/project/horizontal-suggestions.js new file mode 100644 index 0000000000..2473bb1313 --- /dev/null +++ b/packages/template-retail-react-app/app/theme/components/project/horizontal-suggestions.js @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2021, salesforce.com, inc. + * All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +export default { + baseStyle: { + container: { + // Main container for horizontal suggestions + }, + flexContainer: { + gap: 4, + overflowX: 'auto', + pb: 2 + }, + suggestionItem: { + width: { + base: '50vw', + md: '50vw', + lg: '10vw' + }, + flex: '0 0 auto' + }, + imageContainer: { + mb: 2 + }, + aspectRatio: { + ratio: 1 + }, + dynamicImage: { + height: '100%', + width: '100%', + '& picture': { + display: 'block', + height: '100%', + width: '100%' + }, + '& img': { + display: 'block', + height: '100%', + width: '100%', + objectFit: 'cover' + } + }, + productName: { + fontSize: 'sm', + fontWeight: 'medium', + color: 'gray.900', + mb: 1, + noOfLines: 2 + }, + productPrice: { + fontSize: 'sm', + color: 'gray.900', + fontWeight: 'medium' + } + } +} diff --git a/packages/template-retail-react-app/app/theme/components/project/search-suggestions.js b/packages/template-retail-react-app/app/theme/components/project/search-suggestions.js new file mode 100644 index 0000000000..f64d7dd245 --- /dev/null +++ b/packages/template-retail-react-app/app/theme/components/project/search-suggestions.js @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2021, salesforce.com, inc. + * All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +export default { + baseStyle: { + container: { + padding: 6, + spacing: 0 + }, + sectionHeader: { + fontWeight: 200, + margin: '2 0 1 0', + paddingLeft: 12, + color: 'gray.500', + fontSize: 'sm', + lineHeight: 1.2 + }, + phraseContainer: { + margin: '2 0 1 0', + paddingLeft: 12 + }, + suggestionsContainer: { + spacing: 0 + }, + suggestionsBox: { + mx: '-16px' + // borderBottom: '1px solid', + // borderColor: 'gray.200' + }, + suggestionButton: { + width: 'full', + fontSize: 'md', + marginTop: 0, + variant: 'menu-link', + style: { + justifyContent: 'flex-start', + padding: '8px 12px' + } + }, + imageContainer: { + width: 10, + height: 10, + marginRight: 4, + borderRadius: 'full', + background: 'transparent', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + overflow: 'hidden' + }, + suggestionImage: { + boxSize: 10, + borderRadius: 'full', + objectFit: 'cover', + background: '#f3f3f3' + }, + textContainer: { + textAlign: 'left' + }, + suggestionName: { + fontWeight: '500', + as: 'span' + }, + brandName: { + fontWeight: '700', + as: 'span' + }, + categoryParent: { + as: 'span', + color: 'gray.500', + fontSize: 'sm' + }, + badgeGroup: { + position: 'absolute', + top: 2, + left: 2 + } + } +} diff --git a/packages/template-retail-react-app/app/theme/index.js b/packages/template-retail-react-app/app/theme/index.js index df2077f17a..5da80d9ecb 100644 --- a/packages/template-retail-react-app/app/theme/index.js +++ b/packages/template-retail-react-app/app/theme/index.js @@ -50,6 +50,8 @@ import ProductTile from '@salesforce/retail-react-app/app/theme/components/proje import SocialIcons from '@salesforce/retail-react-app/app/theme/components/project/social-icons' import SwatchGroup from '@salesforce/retail-react-app/app/theme/components/project/swatch-group' import ImageGallery from '@salesforce/retail-react-app/app/theme/components/project/image-gallery' +import SearchSuggestions from '@salesforce/retail-react-app/app/theme/components/project/search-suggestions' +import HorizontalSuggestions from '@salesforce/retail-react-app/app/theme/components/project/horizontal-suggestions' // Please refer to the Chakra-Ui theme customization docs found // here https://chakra-ui.com/docs/theming/customize-theme to learn @@ -97,7 +99,9 @@ export const overrides = { Pagination, ProductTile, SwatchGroup, - ImageGallery + ImageGallery, + SearchSuggestions, + HorizontalSuggestions } } diff --git a/packages/template-retail-react-app/package.json b/packages/template-retail-react-app/package.json index 9fc34957ec..22406f8a1d 100644 --- a/packages/template-retail-react-app/package.json +++ b/packages/template-retail-react-app/package.json @@ -100,7 +100,7 @@ "bundlesize": [ { "path": "build/main.js", - "maxSize": "66 kB" + "maxSize": "67 kB" }, { "path": "build/vendor.js", diff --git a/packages/template-retail-react-app/translations/en-GB.json b/packages/template-retail-react-app/translations/en-GB.json index 8c23e59240..34c762dfd3 100644 --- a/packages/template-retail-react-app/translations/en-GB.json +++ b/packages/template-retail-react-app/translations/en-GB.json @@ -1439,6 +1439,18 @@ "search.action.cancel": { "defaultMessage": "Cancel" }, + "search.suggestions.categories": { + "defaultMessage": "Categories" + }, + "search.suggestions.didYouMean": { + "defaultMessage": "Did you mean" + }, + "search.suggestions.products": { + "defaultMessage": "Products" + }, + "search.suggestions.viewAll": { + "defaultMessage": "View All" + }, "selected_refinements.action.assistive_msg.clear_all": { "defaultMessage": "Clear all filters" }, diff --git a/packages/template-retail-react-app/translations/en-US.json b/packages/template-retail-react-app/translations/en-US.json index 8c23e59240..34c762dfd3 100644 --- a/packages/template-retail-react-app/translations/en-US.json +++ b/packages/template-retail-react-app/translations/en-US.json @@ -1439,6 +1439,18 @@ "search.action.cancel": { "defaultMessage": "Cancel" }, + "search.suggestions.categories": { + "defaultMessage": "Categories" + }, + "search.suggestions.didYouMean": { + "defaultMessage": "Did you mean" + }, + "search.suggestions.products": { + "defaultMessage": "Products" + }, + "search.suggestions.viewAll": { + "defaultMessage": "View All" + }, "selected_refinements.action.assistive_msg.clear_all": { "defaultMessage": "Clear all filters" },