Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions packages/react/src/__snapshots__/golden-template.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -89,27 +89,27 @@ exports[`Golden Template: Marketing Email > snapshot locks the full email HTML 1

<!--[if (mso)|(IE)]><td style="padding:5px 15px"><![endif]-->

<span aria-label="Menu item - Shop" style="padding:5px 15px;display:inline-block;color:#444444;font-size:14px;" class="v-padding v-font-size v-layout-display v-font-weight v-letter-spacing">
Shop
</span>
<a aria-label="Menu item - Shop" href="/shop" target="_blank" style="padding:5px 15px;display:inline-block;color:#0068A5;font-size:14px;text-decoration:none" class="v-padding v-font-size v-layout-display v-font-weight v-letter-spacing">
Shop
</a>

<!--[if (mso)|(IE)]></td><![endif]-->


<!--[if (mso)|(IE)]><td style="padding:5px 15px"><![endif]-->

<span aria-label="Menu item - About" style="padding:5px 15px;display:inline-block;color:#444444;font-size:14px;" class="v-padding v-font-size v-layout-display v-font-weight v-letter-spacing">
About
</span>
<a aria-label="Menu item - About" href="/about" target="_blank" style="padding:5px 15px;display:inline-block;color:#0068A5;font-size:14px;text-decoration:none" class="v-padding v-font-size v-layout-display v-font-weight v-letter-spacing">
About
</a>

<!--[if (mso)|(IE)]></td><![endif]-->


<!--[if (mso)|(IE)]><td style="padding:5px 15px"><![endif]-->

<span aria-label="Menu item - Contact" style="padding:5px 15px;display:inline-block;color:#444444;font-size:14px;" class="v-padding v-font-size v-layout-display v-font-weight v-letter-spacing">
Contact
</span>
<a aria-label="Menu item - Contact" href="/contact" target="_blank" style="padding:5px 15px;display:inline-block;color:#0068A5;font-size:14px;text-decoration:none" class="v-padding v-font-size v-layout-display v-font-weight v-letter-spacing">
Contact
</a>

<!--[if (mso)|(IE)]></td><![endif]-->

Expand Down Expand Up @@ -175,7 +175,7 @@ exports[`Golden Template: Marketing Email > snapshot locks the full email HTML 1
<!--[if mso]><style>.v-button {background: transparent !important;}</style><![endif]-->
<div class="v-text-align" align="center">
<!--[if mso]><v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" href="" style="height:45px; v-text-anchor:middle; width:120px;" arcsize="13.5%" stroke="f" fillcolor="#e63946"><w:anchorlock/><center style="color:#ffffff;"><![endif]-->
<a href="" class="v-button v-size-width v-button-colors v-border v-border-radius v-font-family v-font-size v-font-weight v-letter-spacing" style="box-sizing: border-box;display: inline-block;text-decoration: none;-webkit-text-size-adjust: none;text-align: center;color: #ffffff; background-color: #e63946; border-radius: 6px;-webkit-border-radius: 6px; -moz-border-radius: 6px; width:auto; max-width:100%; overflow-wrap: break-word; word-break: break-word; word-wrap:break-word; mso-border-alt: none;font-size: 16px;">
<a href="" target="_blank" class="v-button v-size-width v-button-colors v-border v-border-radius v-font-family v-font-size v-font-weight v-letter-spacing" style="box-sizing: border-box;display: inline-block;text-decoration: none;-webkit-text-size-adjust: none;text-align: center;color: #ffffff; background-color: #e63946; border-radius: 6px;-webkit-border-radius: 6px; -moz-border-radius: 6px; width:auto; max-width:100%; overflow-wrap: break-word; word-break: break-word; word-wrap:break-word; mso-border-alt: none;font-size: 16px;">
<span class="v-line-height v-padding" style="display:block;padding:10px 20px;line-height:120%;">Shop Now</span>
</a>
<!--[if mso]></center></v:roundrect><![endif]-->
Expand Down Expand Up @@ -284,7 +284,7 @@ exports[`Golden Template: Marketing Email > snapshot locks the full email HTML 1
<!--[if mso]><style>.v-button {background: transparent !important;}</style><![endif]-->
<div class="v-text-align" align="center">
<!--[if mso]><v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" href="" style="height:45px; v-text-anchor:middle; width:120px;" arcsize="9%" stroke="f" fillcolor="#1a1a2e"><w:anchorlock/><center style="color:#ffffff;"><![endif]-->
<a href="" class="v-button v-size-width v-button-colors v-border v-border-radius v-font-family v-font-size v-font-weight v-letter-spacing" style="box-sizing: border-box;display: inline-block;text-decoration: none;-webkit-text-size-adjust: none;text-align: center;color: #ffffff; background-color: #1a1a2e; border-radius: 4px;-webkit-border-radius: 4px; -moz-border-radius: 4px; width:auto; max-width:100%; overflow-wrap: break-word; word-break: break-word; word-wrap:break-word; mso-border-alt: none;font-size: 14px;">
<a href="" target="_blank" class="v-button v-size-width v-button-colors v-border v-border-radius v-font-family v-font-size v-font-weight v-letter-spacing" style="box-sizing: border-box;display: inline-block;text-decoration: none;-webkit-text-size-adjust: none;text-align: center;color: #ffffff; background-color: #1a1a2e; border-radius: 4px;-webkit-border-radius: 4px; -moz-border-radius: 4px; width:auto; max-width:100%; overflow-wrap: break-word; word-break: break-word; word-wrap:break-word; mso-border-alt: none;font-size: 14px;">
<span class="v-line-height v-padding" style="display:block;padding:10px 20px;line-height:120%;">Buy</span>
</a>
<!--[if mso]></center></v:roundrect><![endif]-->
Expand Down Expand Up @@ -316,7 +316,7 @@ exports[`Golden Template: Marketing Email > snapshot locks the full email HTML 1
<!--[if mso]><style>.v-button {background: transparent !important;}</style><![endif]-->
<div class="v-text-align" align="center">
<!--[if mso]><v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" href="" style="height:45px; v-text-anchor:middle; width:120px;" arcsize="9%" stroke="f" fillcolor="#1a1a2e"><w:anchorlock/><center style="color:#ffffff;"><![endif]-->
<a href="" class="v-button v-size-width v-button-colors v-border v-border-radius v-font-family v-font-size v-font-weight v-letter-spacing" style="box-sizing: border-box;display: inline-block;text-decoration: none;-webkit-text-size-adjust: none;text-align: center;color: #ffffff; background-color: #1a1a2e; border-radius: 4px;-webkit-border-radius: 4px; -moz-border-radius: 4px; width:auto; max-width:100%; overflow-wrap: break-word; word-break: break-word; word-wrap:break-word; mso-border-alt: none;font-size: 14px;">
<a href="" target="_blank" class="v-button v-size-width v-button-colors v-border v-border-radius v-font-family v-font-size v-font-weight v-letter-spacing" style="box-sizing: border-box;display: inline-block;text-decoration: none;-webkit-text-size-adjust: none;text-align: center;color: #ffffff; background-color: #1a1a2e; border-radius: 4px;-webkit-border-radius: 4px; -moz-border-radius: 4px; width:auto; max-width:100%; overflow-wrap: break-word; word-break: break-word; word-wrap:break-word; mso-border-alt: none;font-size: 14px;">
<span class="v-line-height v-padding" style="display:block;padding:10px 20px;line-height:120%;">Buy</span>
</a>
<!--[if mso]></center></v:roundrect><![endif]-->
Expand Down
57 changes: 57 additions & 0 deletions packages/react/src/components/Button.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,61 @@ describe("Button Component", () => {
it("has correct displayName", () => {
expect(Button.displayName).toBe("Button");
});

// Regression: every accepted href shape must reach the rendered <a>.
// Previously they all rendered as href="" because the mapper handed the
// exporter the schema storage shape `{ name, values: { href, target } }`
// but the exporter reads `e.url`. See semantic-props normalizeLinkValue.
describe("href is rendered to the anchor (regression)", () => {
const URL = "https://example.com";

function getHref(container: HTMLElement): string | null {
return container.querySelector("a")?.getAttribute("href") ?? null;
}

it("renders href when passed as a string", () => {
const { container } = render(<Button href={URL}>Go</Button>);
expect(getHref(container)).toBe(URL);
});

it("renders href when passed as the storage shape {name, values}", () => {
const { container } = render(
<Button
href={
{ name: "web", values: { href: URL, target: "_blank" } } as any
}
>
Go
</Button>
);
expect(getHref(container)).toBe(URL);
});

it("renders href when passed via the values escape hatch", () => {
const { container } = render(
<Button
values={
{
href: {
name: "web",
values: { href: URL, target: "_blank" },
},
} as any
}
>
Go
</Button>
);
expect(getHref(container)).toBe(URL);
});

it("renders href in email mode (table-based output)", () => {
const { container } = render(
<Button mode="email" href={URL}>
Go
</Button>
);
expect(getHref(container)).toBe(URL);
});
});
});
44 changes: 44 additions & 0 deletions packages/react/src/components/Image.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,48 @@ describe("Image Component", () => {
it("has correct displayName", () => {
expect(Image.displayName).toBe("Image");
});

// Regression: action shapes (string / storage / render) must all wrap the
// image in a working <a>. Previously the storage shape silently produced
// no anchor at all because the exporter read `e.url` on `{ name, values }`.
describe("action wraps the image in an anchor (regression)", () => {
const URL = "https://example.com";
const SRC = "https://example.com/photo.jpg";

function getAnchorHref(container: HTMLElement): string | null {
return container.querySelector("a")?.getAttribute("href") ?? null;
}

it("renders anchor href from string action", () => {
const { container } = render(
<Image src={SRC} action={URL as any} />
);
expect(getAnchorHref(container)).toBe(URL);
});

it("renders anchor href from storage-shape action", () => {
const { container } = render(
<Image
src={SRC}
action={
{ name: "web", values: { href: URL, target: "_blank" } } as any
}
/>
);
expect(getAnchorHref(container)).toBe(URL);
});

it("renders anchor href in email mode", () => {
const { container } = render(
<Image
mode="email"
src={SRC}
action={
{ name: "web", values: { href: URL, target: "_blank" } } as any
}
/>
);
expect(getAnchorHref(container)).toBe(URL);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ exports[`Render Snapshots > Body + Row + Column + Items > full tree email 1`] =
<!--[if mso]><style>.v-button {background: transparent !important;}</style><![endif]-->
<div class="v-text-align" align="center">
<!--[if mso]><v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" href="" style="height:45px; v-text-anchor:middle; width:120px;" arcsize="9%" stroke="f" fillcolor="#0879A1"><w:anchorlock/><center style="color:#FFFFFF;"><![endif]-->
<a href="" class="v-button v-size-width v-button-colors v-border v-border-radius v-font-family v-font-size v-font-weight v-letter-spacing" style="box-sizing: border-box;display: inline-block;text-decoration: none;-webkit-text-size-adjust: none;text-align: center;color: #FFFFFF; background-color: #0879A1; border-radius: 4px;-webkit-border-radius: 4px; -moz-border-radius: 4px; width:auto; max-width:100%; overflow-wrap: break-word; word-break: break-word; word-wrap:break-word; mso-border-alt: none;font-size: 14px;">
<a href="" target="_blank" class="v-button v-size-width v-button-colors v-border v-border-radius v-font-family v-font-size v-font-weight v-letter-spacing" style="box-sizing: border-box;display: inline-block;text-decoration: none;-webkit-text-size-adjust: none;text-align: center;color: #FFFFFF; background-color: #0879A1; border-radius: 4px;-webkit-border-radius: 4px; -moz-border-radius: 4px; width:auto; max-width:100%; overflow-wrap: break-word; word-break: break-word; word-wrap:break-word; mso-border-alt: none;font-size: 14px;">
<span class="v-line-height v-padding" style="display:block;padding:10px 20px;line-height:120%;">Click</span>
</a>
<!--[if mso]></center></v:roundrect><![endif]-->
Expand Down Expand Up @@ -117,7 +117,7 @@ exports[`Render Snapshots > Body + Row + Column + Items > full tree web 1`] = `
</div>
</div><div id="u_content_button_2" class="u_content_button" style="padding: 0px;">
<div class="v-text-align" style="text-align: center;">
<a href="" class="v-size-width v-line-height v-padding v-button-colors v-border v-border-radius v-font-family v-font-size v-font-weight v-letter-spacing" style="color:#FFFFFF;background-color:#0879A1;border-radius: 4px;line-height:120%;display:inline-block;text-decoration:none;text-align:center;padding:10px 20px;width:auto;max-width:100%;word-wrap:break-word;font-size: 14px;">
<a href="" target="_blank" class="v-size-width v-line-height v-padding v-button-colors v-border v-border-radius v-font-family v-font-size v-font-weight v-letter-spacing" style="color:#FFFFFF;background-color:#0879A1;border-radius: 4px;line-height:120%;display:inline-block;text-decoration:none;text-align:center;padding:10px 20px;width:auto;max-width:100%;word-wrap:break-word;font-size: 14px;">
Click
</a>
</div>
Expand Down Expand Up @@ -182,7 +182,7 @@ exports[`Render Snapshots > Button > email 1`] = `
<!--[if mso]><style>.v-button {background: transparent !important;}</style><![endif]-->
<div class="v-text-align" align="center">
<!--[if mso]><v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" href="" style="height:45px; v-text-anchor:middle; width:120px;" arcsize="9%" stroke="f" fillcolor="#0879A1"><w:anchorlock/><center style="color:#FFFFFF;"><![endif]-->
<a href="" class="v-button v-size-width v-button-colors v-border v-border-radius v-font-family v-font-size v-font-weight v-letter-spacing" style="box-sizing: border-box;display: inline-block;text-decoration: none;-webkit-text-size-adjust: none;text-align: center;color: #FFFFFF; background-color: #0879A1; border-radius: 4px;-webkit-border-radius: 4px; -moz-border-radius: 4px; width:auto; max-width:100%; overflow-wrap: break-word; word-break: break-word; word-wrap:break-word; mso-border-alt: none;font-size: 14px;">
<a href="" target="_blank" class="v-button v-size-width v-button-colors v-border v-border-radius v-font-family v-font-size v-font-weight v-letter-spacing" style="box-sizing: border-box;display: inline-block;text-decoration: none;-webkit-text-size-adjust: none;text-align: center;color: #FFFFFF; background-color: #0879A1; border-radius: 4px;-webkit-border-radius: 4px; -moz-border-radius: 4px; width:auto; max-width:100%; overflow-wrap: break-word; word-break: break-word; word-wrap:break-word; mso-border-alt: none;font-size: 14px;">
<span class="v-line-height v-padding" style="display:block;padding:10px 20px;line-height:120%;">Click me</span>
</a>
<!--[if mso]></center></v:roundrect><![endif]-->
Expand All @@ -193,7 +193,7 @@ exports[`Render Snapshots > Button > email 1`] = `
exports[`Render Snapshots > Button > web 1`] = `
"<div>
<div class="v-text-align" style="text-align: center;">
<a href="" class="v-size-width v-line-height v-padding v-button-colors v-border v-border-radius v-font-family v-font-size v-font-weight v-letter-spacing" style="color:#FFFFFF;background-color:#0879A1;border-radius: 4px;line-height:120%;display:inline-block;text-decoration:none;text-align:center;padding:10px 20px;width:auto;max-width:100%;word-wrap:break-word;font-size: 14px;">
<a href="" target="_blank" class="v-size-width v-line-height v-padding v-button-colors v-border v-border-radius v-font-family v-font-size v-font-weight v-letter-spacing" style="color:#FFFFFF;background-color:#0879A1;border-radius: 4px;line-height:120%;display:inline-block;text-decoration:none;text-align:center;padding:10px 20px;width:auto;max-width:100%;word-wrap:break-word;font-size: 14px;">
Click me
</a>
</div>
Expand Down
15 changes: 12 additions & 3 deletions packages/react/src/utils/create-component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type { RenderMode, UnlayerConfig } from "@unlayer-internal/shared-element
import {
mergeValues,
generateHtmlFromTextJson,
normalizeValuesForExporter,
DEFAULT_CONFIG,
} from "@unlayer-internal/shared-elements";

Expand Down Expand Up @@ -273,13 +274,21 @@ export function createItemComponent<
...bodyValues
};

// 5. Resolve exporter for this mode (fallback to web)
// 5. Resolve link/action fields to the exporter's render-value shape.
// Skipped on the renderToJson path (which uses propMapper directly)
// so JSON output preserves the schema's storage shape.
const valuesForExporter = normalizeValuesForExporter(
valuesWithMeta as Record<string, any>,
config.name
);

// 6. Resolve exporter for this mode (fallback to web)
const exporter = (config.exporters[mode] || config.exporters.web)!;

// 6. Render using utility (handles all boilerplate)
// 7. Render using utility (handles all boilerplate)
return renderComponent<TValues>({
type: config.name,
values: valuesWithMeta,
values: valuesForExporter as TValues,
mode,
className,
style,
Expand Down
6 changes: 5 additions & 1 deletion packages/shared/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,11 @@ export {
export { mergeValues } from "./utils/merge-values";

// Utils - Semantic props
export { mapSemanticProps } from "./utils/semantic-props";
export {
mapSemanticProps,
normalizeLinkValue,
normalizeValuesForExporter,
} from "./utils/semantic-props";
export type { SemanticProps } from "./utils/semantic-props";

// Utils - HTML to plain text
Expand Down
Loading
Loading