@@ -1286,10 +1286,222 @@ func TestCompileTemplateWithPreserveLiquid(t *testing.T) {
12861286 })
12871287}
12881288
1289+ // TestGomjmlButtonAttributeSupport tests which MJML button attributes are properly
1290+ // supported by the gomjml library (https://documentation.mjml.io/#mj-button)
1291+ func TestGomjmlButtonAttributeSupport (t * testing.T ) {
1292+ // Create button with various MJML-spec attributes
1293+ buttonBase := NewBaseBlock ("button-1" , MJMLComponentMjButton )
1294+ buttonBase .Attributes ["href" ] = "https://example.com"
1295+ buttonBase .Attributes ["font-weight" ] = "bold"
1296+ buttonBase .Attributes ["font-style" ] = "italic"
1297+ buttonBase .Attributes ["text-decoration" ] = "underline"
1298+ buttonBase .Attributes ["text-transform" ] = "uppercase"
1299+ buttonBase .Attributes ["color" ] = "#ff0000"
1300+ buttonBase .Attributes ["background-color" ] = "#00ff00"
1301+ buttonBase .Content = stringPtr ("Click Here" )
1302+ buttonBlock := & MJButtonBlock {BaseBlock : buttonBase }
1303+
1304+ // Create complete MJML structure
1305+ column := & MJColumnBlock {BaseBlock : NewBaseBlock ("column-1" , MJMLComponentMjColumn )}
1306+ column .Children = []EmailBlock {buttonBlock }
1307+
1308+ section := & MJSectionBlock {BaseBlock : NewBaseBlock ("section-1" , MJMLComponentMjSection )}
1309+ section .Children = []EmailBlock {column }
1310+
1311+ body := & MJBodyBlock {BaseBlock : NewBaseBlock ("body-1" , MJMLComponentMjBody )}
1312+ body .Children = []EmailBlock {section }
1313+
1314+ mjml := & MJMLBlock {BaseBlock : NewBaseBlock ("mjml-1" , MJMLComponentMjml )}
1315+ mjml .Children = []EmailBlock {body }
1316+
1317+ req := CompileTemplateRequest {
1318+ WorkspaceID : "test-workspace" ,
1319+ MessageID : "test-message" ,
1320+ VisualEditorTree : mjml ,
1321+ TrackingSettings : TrackingSettings {
1322+ EnableTracking : false ,
1323+ },
1324+ }
1325+
1326+ resp , err := CompileTemplate (req )
1327+ if err != nil {
1328+ t .Fatalf ("CompileTemplate failed: %v" , err )
1329+ }
1330+
1331+ if ! resp .Success {
1332+ t .Fatalf ("Expected successful compilation, got error: %v" , resp .Error )
1333+ }
1334+
1335+ // Log MJML for debugging
1336+ t .Logf ("Generated MJML:\n %s" , * resp .MJML )
1337+
1338+ // Check attribute support in generated HTML
1339+ attributeChecks := []struct {
1340+ name string
1341+ pattern string
1342+ required bool // If true, test fails when not found
1343+ }{
1344+ {"font-weight:bold" , "font-weight:bold" , false },
1345+ {"font-style:italic" , "font-style:italic" , false },
1346+ {"text-decoration" , "text-decoration" , false },
1347+ {"text-transform:uppercase" , "text-transform:uppercase" , false },
1348+ {"color #ff0000" , "#ff0000" , false },
1349+ {"background #00ff00" , "#00ff00" , false },
1350+ {"Button text 'Click Here'" , "Click Here" , true },
1351+ }
1352+
1353+ t .Log ("\n === gomjml Button Attribute Support ===" )
1354+ for _ , check := range attributeChecks {
1355+ found := strings .Contains (* resp .HTML , check .pattern )
1356+ status := "NOT FOUND"
1357+ if found {
1358+ status = "FOUND"
1359+ }
1360+ t .Logf ("%s: %s" , check .name , status )
1361+
1362+ if check .required && ! found {
1363+ t .Errorf ("Required pattern %q not found in HTML" , check .pattern )
1364+ }
1365+ }
1366+
1367+ // Check that default "Button" text is NOT present (our fix should work)
1368+ if strings .Contains (* resp .HTML , ">Button<" ) {
1369+ t .Error ("Found default 'Button' text - custom text was not rendered" )
1370+ }
1371+ }
1372+
12891373// TestCompileTemplateWithImageLiquidOnlySrcPartialData tests the bug scenario from issue #226
12901374// where an mj-image has only Liquid syntax in src (e.g., "{{ postImage }}") and template data
12911375// exists but doesn't include the referenced variable. This would cause the Liquid engine
12921376// to render the variable as an empty string, resulting in src="" which breaks MJML compilation.
1377+ // TestCompileTemplateButtonWithHTMLContent tests the bug from GitHub issue #242
1378+ // where button content containing HTML tags like <strong> and <br> renders as "Button"
1379+ // instead of the custom text. The MJML spec requires button content to be plain text.
1380+ func TestCompileTemplateButtonWithHTMLContent (t * testing.T ) {
1381+ // This test confirms the bug: when button content contains HTML like
1382+ // <strong>Click here for the recipe!</strong><br/>
1383+ // the button renders as "Button" instead of the custom text
1384+
1385+ tests := []struct {
1386+ name string
1387+ buttonContent string
1388+ expectedText string
1389+ shouldContain []string
1390+ shouldNotContain []string
1391+ }{
1392+ {
1393+ name : "plain text button content" ,
1394+ buttonContent : "Click here" ,
1395+ expectedText : "Click here" ,
1396+ shouldContain : []string {"Click here" },
1397+ },
1398+ {
1399+ name : "button with strong tag - BUG #242" ,
1400+ buttonContent : "<strong>Click here for the recipe!</strong>" ,
1401+ expectedText : "Click here for the recipe!" ,
1402+ shouldContain : []string {"Click here for the recipe!" },
1403+ // Should NOT render as default "Button" text
1404+ shouldNotContain : []string {">Button<" },
1405+ },
1406+ {
1407+ name : "button with strong and br tags - BUG #242" ,
1408+ buttonContent : "<strong>Click here for the recipe!</strong><br/>" ,
1409+ expectedText : "Click here for the recipe!" ,
1410+ shouldContain : []string {"Click here for the recipe!" },
1411+ shouldNotContain : []string {">Button<" },
1412+ },
1413+ {
1414+ name : "button with br tag only" ,
1415+ buttonContent : "Line 1<br/>Line 2" ,
1416+ expectedText : "Line 1" ,
1417+ shouldContain : []string {"Line 1" , "Line 2" },
1418+ shouldNotContain : []string {">Button<" },
1419+ },
1420+ {
1421+ name : "button with em tag" ,
1422+ buttonContent : "<em>Important</em> Action" ,
1423+ expectedText : "Important Action" ,
1424+ shouldContain : []string {"Important" , "Action" },
1425+ shouldNotContain : []string {">Button<" },
1426+ },
1427+ {
1428+ name : "button with nested formatting" ,
1429+ buttonContent : "<strong><em>Bold Italic</em></strong>" ,
1430+ expectedText : "Bold Italic" ,
1431+ shouldContain : []string {"Bold Italic" },
1432+ shouldNotContain : []string {">Button<" },
1433+ },
1434+ }
1435+
1436+ for _ , tt := range tests {
1437+ t .Run (tt .name , func (t * testing.T ) {
1438+ // Create button with the test content
1439+ buttonBase := NewBaseBlock ("button-1" , MJMLComponentMjButton )
1440+ buttonBase .Attributes ["href" ] = "https://example.com"
1441+ buttonBase .Content = stringPtr (tt .buttonContent )
1442+ buttonBlock := & MJButtonBlock {BaseBlock : buttonBase }
1443+
1444+ // Create complete MJML structure
1445+ column := & MJColumnBlock {BaseBlock : NewBaseBlock ("column-1" , MJMLComponentMjColumn )}
1446+ column .Children = []EmailBlock {buttonBlock }
1447+
1448+ section := & MJSectionBlock {BaseBlock : NewBaseBlock ("section-1" , MJMLComponentMjSection )}
1449+ section .Children = []EmailBlock {column }
1450+
1451+ body := & MJBodyBlock {BaseBlock : NewBaseBlock ("body-1" , MJMLComponentMjBody )}
1452+ body .Children = []EmailBlock {section }
1453+
1454+ mjml := & MJMLBlock {BaseBlock : NewBaseBlock ("mjml-1" , MJMLComponentMjml )}
1455+ mjml .Children = []EmailBlock {body }
1456+
1457+ req := CompileTemplateRequest {
1458+ WorkspaceID : "test-workspace" ,
1459+ MessageID : "test-message" ,
1460+ VisualEditorTree : mjml ,
1461+ TrackingSettings : TrackingSettings {
1462+ EnableTracking : false ,
1463+ },
1464+ }
1465+
1466+ resp , err := CompileTemplate (req )
1467+ if err != nil {
1468+ t .Fatalf ("CompileTemplate failed: %v" , err )
1469+ }
1470+
1471+ if ! resp .Success {
1472+ t .Fatalf ("Expected successful compilation, got error: %v" , resp .Error )
1473+ }
1474+
1475+ if resp .HTML == nil {
1476+ t .Fatal ("Expected HTML in response" )
1477+ }
1478+
1479+ // Log MJML and HTML for debugging
1480+ t .Logf ("Input button content: %s" , tt .buttonContent )
1481+ t .Logf ("Generated MJML:\n %s" , * resp .MJML )
1482+ t .Logf ("Generated HTML (excerpt):\n %s" , * resp .HTML )
1483+
1484+ // Check that expected content appears in HTML
1485+ for _ , expected := range tt .shouldContain {
1486+ if ! strings .Contains (* resp .HTML , expected ) {
1487+ t .Errorf ("Expected HTML to contain %q for button text, but it didn't.\n " +
1488+ "This confirms bug #242: button content with HTML tags doesn't render correctly.\n " +
1489+ "HTML output:\n %s" , expected , * resp .HTML )
1490+ }
1491+ }
1492+
1493+ // Check that unexpected content does NOT appear
1494+ for _ , unexpected := range tt .shouldNotContain {
1495+ if strings .Contains (* resp .HTML , unexpected ) {
1496+ t .Errorf ("Expected HTML NOT to contain %q (default button text), but it did.\n " +
1497+ "This confirms bug #242: button fell back to default 'Button' text.\n " +
1498+ "HTML output:\n %s" , unexpected , * resp .HTML )
1499+ }
1500+ }
1501+ })
1502+ }
1503+ }
1504+
12931505func TestCompileTemplateWithImageLiquidOnlySrcPartialData (t * testing.T ) {
12941506 // Create an mj-image with only Liquid syntax in src attribute
12951507 imageBase := NewBaseBlock ("image-1" , MJMLComponentMjImage )
0 commit comments