using AngleSharp.Html.Parser;
namespace XFESystem.Browser.Tests;
public sealed class BrowserCssomTests
{
[Fact]
public void ConstructedStylesheetsParseRealCssAndExposeLiveRuleLists()
{
Run("""
const sheet=new CSSStyleSheet(),rules=sheet.cssRules;
if(rules.length!==0||sheet.ownerNode!==null||sheet.href!==null)throw Error('new');
if(sheet.insertRule(':root { --mermaid-font-family: Segoe UI; }',0)!==0)throw Error('index');
sheet.insertRule('.node rect { fill: #123456; stroke: red; }',1);
if(rules.length!==2||!rules[0].cssText.includes('--mermaid-font-family')||!rules[1].cssText.includes('fill'))throw Error('parse');
if([...rules].map(rule=>rule.cssText).join('\n').length<20)throw Error('iterator');
sheet.deleteRule(0);if(rules.length!==1||!rules.item(0).cssText.includes('node'))throw Error('delete');
""");
}
[Fact]
public void ReplaceSyncAndReplaceUpdateTheSameActualConstructedSheet()
{
Run("""
const sheet=new CSSStyleSheet(),rules=sheet.cssRules;sheet.replaceSync('a { color: red } b { color: blue }');
if(rules.length!==2)throw Error('replaceSync');sheet.replace('p { font-size: 18px; }').then(value=>{if(value!==sheet||rules.length!==1||!rules[0].cssText.includes('18px'))throw Error('replace');});
""");
}
[Fact]
public void InvalidRuleIndexesAndMultipleInsertionsAreRejectedWithoutChangingTheSheet()
{
Run("""
const sheet=new CSSStyleSheet();sheet.insertRule('a { color: red }');let invalid=0;
try{sheet.insertRule('p { color: blue }',2);}catch(error){if(error.name==='IndexSizeError')invalid++;}
try{sheet.deleteRule(1);}catch(error){if(error.name==='IndexSizeError')invalid++;}
try{sheet.insertRule('p{}b{}',0);}catch(error){if(error.name==='SyntaxError')invalid++;}
if(invalid!==3||sheet.cssRules.length!==1)throw Error('invalid mutation');
""");
}
[Fact]
public void ConstructedStylesheetCountAndContentAreBounded()
{
var limits = new BrowserLimits { MaximumStyleSheets = 1, MaximumStyleCharacters = 40 };
var runtime = Create(limits);
Assert.True(runtime.Evaluate("var sheet=new CSSStyleSheet();sheet.insertRule('a { color: red }');").Success);
Assert.True(runtime.Evaluate("new CSSStyleSheet()").BudgetExceeded);
Assert.True(runtime.Evaluate("sheet.replaceSync('p { --long-content: '+ 'x'.repeat(80)+'; }')").BudgetExceeded);
Assert.Equal("1", runtime.Evaluate("String(sheet.cssRules.length)").Value);
}
private static void Run(string code)
{
var result = Create(new()).Evaluate(code);
Assert.True(result.Success, result.Error);
}
private static JavaScriptRuntime Create(BrowserLimits limits)
{
var runtime = new JavaScriptRuntime(limits);
var doc = new HtmlParser().ParseDocument("<div></div>");
_ = new DomBridge(runtime, doc, new("http://xfesystem.local/"), limits);
return runtime;
}
}
using AngleSharp.Html.Parser;
namespace XFESystem.Browser.Tests;
public sealed class BrowserCssomTests
{
[Fact]
public void ConstructedStylesheetsParseRealCssAndExposeLiveRuleLists()
{
Run("""
const sheet=new CSSStyleSheet(),rules=sheet.cssRules;
if(rules.length!==0||sheet.ownerNode!==null||sheet.href!==null)throw Error('new');
if(sheet.insertRule(':root { --mermaid-font-family: Segoe UI; }',0)!==0)throw Error('index');
sheet.insertRule('.node rect { fill: #123456; stroke: red; }',1);
if(rules.length!==2||!rules[0].cssText.includes('--mermaid-font-family')||!rules[1].cssText.includes('fill'))throw Error('parse');
if([...rules].map(rule=>rule.cssText).join('\n').length<20)throw Error('iterator');
sheet.deleteRule(0);if(rules.length!==1||!rules.item(0).cssText.includes('node'))throw Error('delete');
""");
}
[Fact]
public void ReplaceSyncAndReplaceUpdateTheSameActualConstructedSheet()
{
Run("""
const sheet=new CSSStyleSheet(),rules=sheet.cssRules;sheet.replaceSync('a { color: red } b { color: blue }');
if(rules.length!==2)throw Error('replaceSync');sheet.replace('p { font-size: 18px; }').then(value=>{if(value!==sheet||rules.length!==1||!rules[0].cssText.includes('18px'))throw Error('replace');});
""");
}
[Fact]
public void InvalidRuleIndexesAndMultipleInsertionsAreRejectedWithoutChangingTheSheet()
{
Run("""
const sheet=new CSSStyleSheet();sheet.insertRule('a { color: red }');let invalid=0;
try{sheet.insertRule('p { color: blue }',2);}catch(error){if(error.name==='IndexSizeError')invalid++;}
try{sheet.deleteRule(1);}catch(error){if(error.name==='IndexSizeError')invalid++;}
try{sheet.insertRule('p{}b{}',0);}catch(error){if(error.name==='SyntaxError')invalid++;}
if(invalid!==3||sheet.cssRules.length!==1)throw Error('invalid mutation');
""");
}
[Fact]
public void ConstructedStylesheetCountAndContentAreBounded()
{
var limits = new BrowserLimits { MaximumStyleSheets = 1, MaximumStyleCharacters = 40 };
var runtime = Create(limits);
Assert.True(runtime.Evaluate("var sheet=new CSSStyleSheet();sheet.insertRule('a { color: red }');").Success);
Assert.True(runtime.Evaluate("new CSSStyleSheet()").BudgetExceeded);
Assert.True(runtime.Evaluate("sheet.replaceSync('p { --long-content: '+ 'x'.repeat(80)+'; }')").BudgetExceeded);
Assert.Equal("1", runtime.Evaluate("String(sheet.cssRules.length)").Value);
}
private static void Run(string code)
{
var result = Create(new()).Evaluate(code);
Assert.True(result.Success, result.Error);
}
private static JavaScriptRuntime Create(BrowserLimits limits)
{
var runtime = new JavaScriptRuntime(limits);
var doc = new HtmlParser().ParseDocument("<div></div>");
_ = new DomBridge(runtime, doc, new("http://xfesystem.local/"), limits);
return runtime;
}
}