using AngleSharp.Html.Parser; namespace XFESystem.Browser.Tests; public sealed class BrowserDatasetTests { [Fact] public void ConsoleCallbacksAreCallableWithStandardFunctionBindAndCall() { Run("console.log.bind(console)('bound');console.warn.call(console,'called');"); } [Fact] public void BoundJavaScriptMethodsCanBeBoundAgain() { Run("class C { constructor(){this.value=42;this.method=this.method.bind(this);this.second=this.method.bind(this);}method(){return this.value;}} if(new C().second()!==42)throw Error('bind twice');"); } [Fact] public void DatasetReflectsExistingAttributesAndStaysTheSameObject() { Run(""" const element=document.querySelector('div'),data=element.dataset; if(data!==element.dataset||!(data instanceof DOMStringMap)||Object.prototype.toString.call(data)!=='[object DOMStringMap]')throw Error('identity'); if(data.fooBar!=='one'||data.empty!==''||data.missing!==undefined)throw Error('values'); element.setAttribute('data-foo-bar','two');if(data.fooBar!=='two')throw Error('attribute reflection'); """); } [Fact] public void DatasetWritesAndDeletesActualCamelCaseAttributes() { var doc = Run(""" const element=document.querySelector('div');element.dataset.codeToolsApplied=true;element.dataset.fooBar=null; if(element.getAttribute('data-code-tools-applied')!=='true'||element.getAttribute('data-foo-bar')!=='null')throw Error('set'); delete element.dataset.fooBar;if(element.hasAttribute('data-foo-bar')||'fooBar' in element.dataset)throw Error('delete'); element.dataset['number-1']='ok';element.dataset['foo-Bar']='dash'; if(element.getAttribute('data-number-1')!=='ok'||element.getAttribute('data-foo--bar')!=='dash')throw Error('special'); """); Assert.Equal("true", doc.QuerySelector("div")!.GetAttribute("data-code-tools-applied")); } [Fact] public void DatasetOwnKeysDescriptorsAndJsonReflectTheLiveAttributeCollection() { Run(""" const element=document.querySelector('div'),data=element.dataset; if(Object.keys(data).join(',')!=='fooBar,empty')throw Error('keys'); const descriptor=Object.getOwnPropertyDescriptor(data,'fooBar'); if(!descriptor.enumerable||!descriptor.configurable||!descriptor.writable||descriptor.value!=='one')throw Error('descriptor'); element.setAttribute('data-new-key','three');if(Object.entries(data).length!==3||JSON.parse(JSON.stringify(data)).newKey!=='three')throw Error('live keys'); delete data.empty;if(Object.keys(data).join(',')!=='fooBar,newKey')throw Error('removed keys'); """); } [Fact] public void DatasetPrototypeNamedPropertiesCannotChangeTheActualPrototype() { Run(""" const data=document.querySelector('div').dataset,prototype=Object.getPrototypeOf(data); data.__proto__='safe';if(data.__proto__!=='safe'||Object.getPrototypeOf(data)!==prototype)throw Error('prototype'); if(!Object.hasOwn(data,'__proto__'))throw Error('own');delete data.__proto__; data.constructor='text';if(data.constructor!=='text')throw Error('constructor');delete data.constructor; if(data.constructor!==DOMStringMap)throw Error('prototype restoration'); """); } [Fact] public void DatasetRejectsAmbiguousHyphenLowercaseNames() { Run(""" const element=document.querySelector('div');let name='';try{element.dataset['foo-bar']='bad';}catch(error){name=error.name;} if(name!=='SyntaxError'||element.getAttribute('data-foo-bar')!=='one')throw Error('invalid name'); Object.defineProperty(element.dataset,'definedKey',{value:'defined',configurable:true,enumerable:true,writable:true}); if(element.getAttribute('data-defined-key')!=='defined')throw Error('define'); """); } private static AngleSharp.Dom.IDocument Run(string code) { var runtime = new JavaScriptRuntime(); var doc = new HtmlParser().ParseDocument("
"); _ = new DomBridge(runtime, doc, new("http://xfesystem.local/"), new()); var result = runtime.Evaluate(code); Assert.True(result.Success, result.Error); return doc; } }