@@ -1767,3 +1767,186 @@ let ``Script transforms to markdown`` () =
17671767 md |> shouldContainText " [substitute-in-href-text: simple1](http://google.com)"
17681768 md |> shouldContainText " Another [hyperlink](simple2.md)"
17691769 md |> shouldContainText " let hello ="
1770+
1771+ // --------------------------------------------------------------------------------------
1772+ // Emoji in FSX comments → HTML (Issue #964)
1773+ // These tests verify that emoji characters are preserved throughout the full
1774+ // FSX → Markdown → HTML conversion pipeline.
1775+ // --------------------------------------------------------------------------------------
1776+
1777+ // Supplementary plane emoji (U+1F389, stored as surrogate pair in UTF-16)
1778+ let emojiParty = " \U0001F389 " // 🎉 PARTY POPPER
1779+ let emojiRocket = " \U0001F680 " // 🚀 ROCKET
1780+ let emojiConstruction = " \U0001F6A7 " // 🚧 CONSTRUCTION SIGN
1781+ // Basic multilingual plane emoji (single UTF-16 code unit)
1782+ let emojiStar = " \u2B50 " // ⭐ WHITE MEDIUM STAR
1783+ let emojiCheck = " \u2705 " // ✅ WHITE HEAVY CHECK MARK
1784+ // Emoji with variation selector (two code points)
1785+ let emojiWarning = " \u26A0\uFE0F " // ⚠️ WARNING SIGN + VS-16
1786+ // ZWJ sequence (multiple code points joined with zero-width joiner)
1787+ let emojiFamily = " \U0001F468\u200D\U0001F469\u200D\U0001F467\u200D\U0001F466 " // 👨👩👧👦
1788+
1789+ [<Test>]
1790+ let ``Supplementary plane emoji in FSX doc comment are preserved in HTML`` () =
1791+ let fsx = sprintf " (**\n Like this %s and %s \n *)\n let x = 42" emojiParty emojiRocket
1792+ let doc = Literate.ParseScriptString( fsx)
1793+ let html = Literate.ToHtml( doc)
1794+ html |> shouldContainText emojiParty
1795+ html |> shouldContainText emojiRocket
1796+
1797+ [<Test>]
1798+ let ``BMP emoji in FSX doc comment are preserved in HTML`` () =
1799+ let fsx = sprintf " (**\n Stars %s and checks %s \n *)\n let x = 42" emojiStar emojiCheck
1800+ let doc = Literate.ParseScriptString( fsx)
1801+ let html = Literate.ToHtml( doc)
1802+ html |> shouldContainText emojiStar
1803+ html |> shouldContainText emojiCheck
1804+
1805+ [<Test>]
1806+ let ``Variation selector emoji in FSX doc comment are preserved in HTML`` () =
1807+ let fsx = sprintf " (**\n Warning %s sign\n *)\n let x = 42" emojiWarning
1808+ let doc = Literate.ParseScriptString( fsx)
1809+ let html = Literate.ToHtml( doc)
1810+ html |> shouldContainText emojiWarning
1811+
1812+ [<Test>]
1813+ let ``ZWJ emoji sequence in FSX doc comment are preserved in HTML`` () =
1814+ let fsx = sprintf " (**\n Family %s emoji\n *)\n let x = 42" emojiFamily
1815+ let doc = Literate.ParseScriptString( fsx)
1816+ let html = Literate.ToHtml( doc)
1817+ html |> shouldContainText emojiFamily
1818+
1819+ [<Test>]
1820+ let ``Emoji in FSX heading ( H1 ) are preserved in HTML`` () =
1821+ let fsx = sprintf " (**\n # Heading %s Title\n *)\n let x = 42" emojiParty
1822+ let doc = Literate.ParseScriptString( fsx)
1823+ let html = Literate.ToHtml( doc)
1824+ html |> shouldContainText emojiParty
1825+
1826+ [<Test>]
1827+ let ``Emoji in FSX heading ( H2 ) are preserved in HTML`` () =
1828+ let fsx = sprintf " (**\n ## Subheading %s \n *)\n let x = 42" emojiRocket
1829+ let doc = Literate.ParseScriptString( fsx)
1830+ let html = Literate.ToHtml( doc)
1831+ html |> shouldContainText emojiRocket
1832+
1833+ [<Test>]
1834+ let ``Emoji in bold spans in FSX doc comment are preserved in HTML`` () =
1835+ let fsx = sprintf " (**\n **Bold %s text**\n *)\n let x = 42" emojiParty
1836+ let doc = Literate.ParseScriptString( fsx)
1837+ let html = Literate.ToHtml( doc)
1838+ html |> shouldContainText emojiParty
1839+
1840+ [<Test>]
1841+ let ``Emoji in italic spans in FSX doc comment are preserved in HTML`` () =
1842+ let fsx = sprintf " (**\n _Italic %s text_\n *)\n let x = 42" emojiStar
1843+ let doc = Literate.ParseScriptString( fsx)
1844+ let html = Literate.ToHtml( doc)
1845+ html |> shouldContainText emojiStar
1846+
1847+ [<Test>]
1848+ let ``Emoji in list items in FSX doc comment are preserved in HTML`` () =
1849+ let fsx = sprintf " (**\n - Item %s \n - Item %s \n *)\n let x = 42" emojiParty emojiCheck
1850+ let doc = Literate.ParseScriptString( fsx)
1851+ let html = Literate.ToHtml( doc)
1852+ html |> shouldContainText emojiParty
1853+ html |> shouldContainText emojiCheck
1854+
1855+ [<Test>]
1856+ let ``Emoji in inline code in FSX doc comment are preserved in HTML`` () =
1857+ let fsx = sprintf " (**\n Code `%s emoji` here\n *)\n let x = 42" emojiParty
1858+ let doc = Literate.ParseScriptString( fsx)
1859+ let html = Literate.ToHtml( doc)
1860+ html |> shouldContainText emojiParty
1861+
1862+ [<Test>]
1863+ let ``All emoji types together in FSX doc comment are all preserved in HTML`` () =
1864+ let fsx =
1865+ sprintf
1866+ " (**\n All: %s %s %s %s %s \n *)\n let x = 42"
1867+ emojiParty
1868+ emojiConstruction
1869+ emojiStar
1870+ emojiWarning
1871+ emojiCheck
1872+
1873+ let doc = Literate.ParseScriptString( fsx)
1874+ let html = Literate.ToHtml( doc)
1875+ html |> shouldContainText emojiParty
1876+ html |> shouldContainText emojiConstruction
1877+ html |> shouldContainText emojiStar
1878+ html |> shouldContainText emojiWarning
1879+ html |> shouldContainText emojiCheck
1880+
1881+ [<Test>]
1882+ let ``Emoji across multiple FSX doc comment blocks are all preserved in HTML`` () =
1883+ let fsx = sprintf " (**\n First block %s \n *)\n let x = 42\n (**\n Second block %s \n *)\n let y = 99" emojiParty emojiRocket
1884+ let doc = Literate.ParseScriptString( fsx)
1885+ let html = Literate.ToHtml( doc)
1886+ html |> shouldContainText emojiParty
1887+ html |> shouldContainText emojiRocket
1888+
1889+ [<Test>]
1890+ let ``Emoji in multi - line FSX doc comment are preserved in HTML`` () =
1891+ let fsx = sprintf " (**\n Line one %s \n Line two %s \n Line three %s \n *)\n let x = 42" emojiParty emojiStar emojiCheck
1892+ let doc = Literate.ParseScriptString( fsx)
1893+ let html = Literate.ToHtml( doc)
1894+ html |> shouldContainText emojiParty
1895+ html |> shouldContainText emojiStar
1896+ html |> shouldContainText emojiCheck
1897+
1898+ [<Test>]
1899+ let ``Emoji do not break HTML escaping in FSX doc comments`` () =
1900+ let fsx = sprintf " (**\n A & %s and <tag>\n *)\n let x = 42" emojiParty
1901+ let doc = Literate.ParseScriptString( fsx)
1902+ let html = Literate.ToHtml( doc)
1903+ html |> shouldContainText emojiParty
1904+ html |> shouldContainText " &"
1905+
1906+ [<Test>]
1907+ let ``Emoji in FSX file on disk are preserved in HTML output`` () =
1908+ let fsx = File.ReadAllText(__ SOURCE_ DIRECTORY__ </> " files" </> " emoji.fsx" )
1909+ let doc = Literate.ParseScriptString( fsx, __ SOURCE_ DIRECTORY__ </> " files" </> " emoji.fsx" )
1910+ let html = Literate.ToHtml( doc)
1911+ html |> shouldContainText emojiParty
1912+ html |> shouldContainText emojiRocket
1913+ html |> shouldContainText emojiStar
1914+ html |> shouldContainText emojiWarning
1915+ html |> shouldContainText emojiCheck
1916+ html |> shouldContainText emojiConstruction
1917+ html |> shouldContainText emojiFamily
1918+
1919+ [<Test>]
1920+ let ``Emoji in ConvertScriptFile HTML output file are preserved`` () =
1921+ let outputFile = __ SOURCE_ DIRECTORY__ </> " output" </> " emoji.html"
1922+
1923+ Literate.ConvertScriptFile(
1924+ __ SOURCE_ DIRECTORY__ </> " files" </> " emoji.fsx" ,
1925+ outputKind = OutputKind.Html,
1926+ output = outputFile
1927+ )
1928+
1929+ let html = File.ReadAllText outputFile
1930+ html |> shouldContainText emojiParty
1931+ html |> shouldContainText emojiRocket
1932+ html |> shouldContainText emojiStar
1933+ html |> shouldContainText emojiWarning
1934+ html |> shouldContainText emojiCheck
1935+ html |> shouldContainText emojiConstruction
1936+
1937+ [<Test>]
1938+ let ``Emoji in ConvertScriptFile Markdown output file are preserved`` () =
1939+ let outputFile = __ SOURCE_ DIRECTORY__ </> " output2" </> " emoji.md"
1940+
1941+ Literate.ConvertScriptFile(
1942+ __ SOURCE_ DIRECTORY__ </> " files" </> " emoji.fsx" ,
1943+ outputKind = OutputKind.Markdown,
1944+ output = outputFile
1945+ )
1946+
1947+ let md = File.ReadAllText outputFile
1948+ md |> shouldContainText emojiParty
1949+ md |> shouldContainText emojiRocket
1950+ md |> shouldContainText emojiStar
1951+
1952+ // End emoji tests
0 commit comments