@salesforce/ui-bundle-template-app-react-sample-b2e 12.8.0 → 12.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/CHANGELOG.md CHANGED
@@ -3,6 +3,22 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [12.10.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v12.9.0...v12.10.0) (2026-10-06)
7
+
8
+ **Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
9
+
10
+
11
+
12
+
13
+
14
+ ## [12.9.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v12.8.0...v12.9.0) (2026-09-29)
15
+
16
+ **Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
17
+
18
+
19
+
20
+
21
+
6
22
  ## [12.8.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v12.7.0...v12.8.0) (2026-09-23)
7
23
 
8
24
  **Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
@@ -19,8 +19,8 @@
19
19
  "graphql:schema": "node scripts/get-graphql-schema.mjs"
20
20
  },
21
21
  "dependencies": {
22
- "@salesforce/platform-sdk": "^12.8.0",
23
- "@salesforce/ui-bundle": "^12.8.0",
22
+ "@salesforce/platform-sdk": "^12.10.0",
23
+ "@salesforce/ui-bundle": "^12.10.0",
24
24
  "@tailwindcss/vite": "^4.1.17",
25
25
  "class-variance-authority": "^0.7.1",
26
26
  "clsx": "^2.1.1",
@@ -47,8 +47,8 @@
47
47
  "@graphql-eslint/eslint-plugin": "^4.1.0",
48
48
  "@graphql-tools/utils": "^11.0.0",
49
49
  "@playwright/test": "^1.49.0",
50
- "@salesforce/graphiti": "^12.8.0",
51
- "@salesforce/vite-plugin-ui-bundle": "^12.8.0",
50
+ "@salesforce/graphiti": "^12.10.0",
51
+ "@salesforce/vite-plugin-ui-bundle": "^12.10.0",
52
52
  "@testing-library/jest-dom": "^6.6.3",
53
53
  "@testing-library/react": "^16.1.0",
54
54
  "@testing-library/user-event": "^14.5.2",
@@ -107,11 +107,7 @@ export function PaginationControls({
107
107
  <Label htmlFor={selectId} className="text-sm font-normal whitespace-nowrap">
108
108
  Results per page:
109
109
  </Label>
110
- <Select
111
- value={pageSize.toString()}
112
- onValueChange={handlePageSizeChange}
113
- disabled={disabled}
114
- >
110
+ <Select value={pageSize.toString()} onValueChange={handlePageSizeChange}>
115
111
  <SelectTrigger id={selectId} className="w-16">
116
112
  <SelectValue />
117
113
  </SelectTrigger>
@@ -20,6 +20,11 @@ import { Link } from "react-router";
20
20
  import type { CmsSourceConfig, CmsSearchItem } from "../../adapters/cms/types";
21
21
  import { formatContentTypeLabel, isValidCmsFqn } from "../../adapters/cms/contentTypeUtils";
22
22
 
23
+ const EM_BLOCK_SPLIT = /(<(?:em)>[\s\S]*?<\/(?:em)>)/g;
24
+ const EM_BLOCK_EXEC = /^<(?:em)>([\s\S]*?)<\/(?:em)>$/;
25
+ const STRAY_TAG = /<\/?[a-zA-Z][^>]*>/g;
26
+ const ROUTE_TOKEN = /:([A-Za-z_][A-Za-z0-9_]*)/g;
27
+
23
28
  /** Formats an ISO date string for display; echoes the raw string on failure. */
24
29
  function formatPublishedDate(dateStr: string): string {
25
30
  const date = new Date(dateStr);
@@ -43,7 +48,7 @@ function resolveRoute(pattern: string, item: CmsSearchItem): string | null {
43
48
  contentType: item.contentType,
44
49
  };
45
50
  let failed = false;
46
- const out = pattern.replace(/:([A-Za-z_][A-Za-z0-9_]*)/g, (_match, token: string) => {
51
+ const out = pattern.replace(ROUTE_TOKEN, (_match, token: string) => {
47
52
  const value = tokens[token];
48
53
  if (value == null || value === "") {
49
54
  failed = true;
@@ -54,6 +59,44 @@ function resolveRoute(pattern: string, item: CmsSearchItem): string | null {
54
59
  return failed ? null : out;
55
60
  }
56
61
 
62
+ /**
63
+ * - Decodes every HTML entity the browser knows (named + numeric).
64
+ * - Runs twice to unwrap the double-encoding CMS search sometimes returns.
65
+ */
66
+ function decodeEntities(input: string): string {
67
+ if (typeof document === "undefined") return input;
68
+ const el = document.createElement("textarea");
69
+ const pass = (s: string) => {
70
+ el.innerHTML = s;
71
+ return el.value;
72
+ };
73
+ return pass(pass(input));
74
+ }
75
+
76
+ /**
77
+ * Parses a snippet that may contain `<em>…</em>` segments and renders it as highlighted text,
78
+ * and strips any other HTML tags so their content renders as plain text.
79
+ */
80
+ function renderHighlightedSnippet(snippet: string): ReactNode[] {
81
+ const decoded = decodeEntities(snippet);
82
+ const parts = decoded.split(EM_BLOCK_SPLIT);
83
+ return parts.map((part, i) => {
84
+ const emphasisMatch = EM_BLOCK_EXEC.exec(part);
85
+ if (emphasisMatch) {
86
+ return (
87
+ <mark key={i} className="bg-yellow-200 dark:bg-yellow-700/60 rounded-sm px-0.5">
88
+ {emphasisMatch[1].replace(STRAY_TAG, "")}
89
+ </mark>
90
+ );
91
+ }
92
+ return part.replace(STRAY_TAG, "");
93
+ });
94
+ }
95
+
96
+ function toPlainText(raw: string): string {
97
+ return decodeEntities(raw).replace(STRAY_TAG, "");
98
+ }
99
+
57
100
  interface CmsResultRowProps {
58
101
  node: unknown;
59
102
  source: CmsSourceConfig;
@@ -71,9 +114,11 @@ export function CmsResultRow({ node, source }: CmsResultRowProps) {
71
114
 
72
115
  const content: ReactNode = (
73
116
  <>
74
- <span className="font-medium">{item.title ?? "—"}</span>
117
+ <span className="font-medium">{item.title ? renderHighlightedSnippet(item.title) : "—"}</span>
75
118
  {item.highlightedSnippet && (
76
- <p className="text-sm text-neutral-600 dark:text-neutral-300">{item.highlightedSnippet}</p>
119
+ <p className="text-sm text-neutral-600 dark:text-neutral-300">
120
+ {renderHighlightedSnippet(item.highlightedSnippet)}
121
+ </p>
77
122
  )}
78
123
  {subtitleParts.length > 0 && (
79
124
  <p className="text-sm text-neutral-600 dark:text-neutral-300">
@@ -92,7 +137,7 @@ export function CmsResultRow({ node, source }: CmsResultRowProps) {
92
137
  <Link
93
138
  to={href}
94
139
  className="block hover:bg-accent rounded-md px-3 -mx-3 py-1 transition-colors"
95
- aria-label={`View content: ${item.title ?? "content"}`}
140
+ aria-label={`View content: ${item.title ? toPlainText(item.title) : "content"}`}
96
141
  >
97
142
  {content}
98
143
  </Link>
@@ -108,6 +108,62 @@ describe("CmsResultRow", () => {
108
108
  });
109
109
  });
110
110
 
111
+ describe("highlight and entity decoding", () => {
112
+ it("converts <em> blocks to <mark> elements", () => {
113
+ const item: CmsSearchItem = {
114
+ ...baseItem,
115
+ highlightedSnippet: "the <em>king</em> returns",
116
+ };
117
+ const container = renderWithRouter(<CmsResultRow node={item} source={source} />);
118
+
119
+ const mark = container.querySelector("mark");
120
+ expect(mark).toBeTruthy();
121
+ expect(mark?.textContent).toBe("king");
122
+ });
123
+
124
+ it("decodes double-encoded entities from CMS search", () => {
125
+ const item: CmsSearchItem = {
126
+ ...baseItem,
127
+ highlightedSnippet: "&amp;#39;<em>king</em>&amp;#39;",
128
+ };
129
+ const container = renderWithRouter(<CmsResultRow node={item} source={source} />);
130
+
131
+ expect(container.textContent).toContain("'king'");
132
+ expect(container.textContent).not.toContain("&amp;");
133
+ });
134
+
135
+ it("strips stray HTML tags but keeps their text content", () => {
136
+ const item: CmsSearchItem = {
137
+ ...baseItem,
138
+ highlightedSnippet: "<em>hi<b>bold</b>there</em> <p>plain</p>",
139
+ };
140
+ const container = renderWithRouter(<CmsResultRow node={item} source={source} />);
141
+
142
+ expect(container.querySelector("mark")?.textContent).toBe("hiboldthere");
143
+ expect(container.innerHTML).not.toContain("<b>");
144
+ expect(container.innerHTML).not.toContain("<p>");
145
+ });
146
+
147
+ it("handles the real CMS response pattern end-to-end", () => {
148
+ const item: CmsSearchItem = {
149
+ ...baseItem,
150
+ title: "Inside the &#39;<em>King</em>&#39; &mdash; What&#39;s Going On",
151
+ highlightedSnippet: "Inside the &amp;#39;<em>king</em>&amp;#39; of the industry.",
152
+ };
153
+ const container = renderWithRouter(<CmsResultRow node={item} source={source} />);
154
+
155
+ expect(container.querySelector("span.font-medium")?.textContent).toContain(
156
+ "Inside the 'King' — What's Going On",
157
+ );
158
+ expect(container.querySelector("span.font-medium mark")?.textContent).toBe("King");
159
+ const snippet = Array.from(container.querySelectorAll("p")).find((p) =>
160
+ p.textContent?.includes("industry"),
161
+ );
162
+ expect(snippet?.querySelector("mark")?.textContent).toBe("king");
163
+ expect(snippet?.textContent).not.toContain("&amp;");
164
+ });
165
+ });
166
+
111
167
  describe("subtitle contrast (WCAG 2.2 SC 1.4.3)", () => {
112
168
  it("renders subtitle and snippet with a contrast-safe class, not text-muted-foreground", () => {
113
169
  const item: CmsSearchItem = {
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@salesforce/ui-bundle-template-base-sfdx-project",
3
- "version": "12.8.0",
3
+ "version": "12.10.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@salesforce/ui-bundle-template-base-sfdx-project",
9
- "version": "12.8.0",
9
+ "version": "12.10.0",
10
10
  "license": "SEE LICENSE IN LICENSE.txt",
11
11
  "dependencies": {
12
12
  "fast-xml-parser": "^5.9.3",
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/ui-bundle-template-base-sfdx-project",
3
- "version": "12.8.0",
3
+ "version": "12.10.0",
4
4
  "description": "Base SFDX project template",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "publishConfig": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/ui-bundle-template-app-react-sample-b2e",
3
- "version": "12.8.0",
3
+ "version": "12.10.0",
4
4
  "description": "Salesforce sample property rental React app",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "author": "",
@@ -16,7 +16,7 @@
16
16
  "clean": "rm -rf dist"
17
17
  },
18
18
  "dependencies": {
19
- "@salesforce/ui-bundle": "^12.8.0",
19
+ "@salesforce/ui-bundle": "^12.10.0",
20
20
  "sonner": "^1.7.0"
21
21
  },
22
22
  "devDependencies": {