@@ -98,6 +98,51 @@ public void GIVEN_ReplaceExternalNavigation_WHEN_NavigateExternallyCalled_THEN_W
9898 _target . GetLastReplacedHref ( ) . Should ( ) . Be ( "https://example.com/path?query=value" ) ;
9999 }
100100
101+ [ Fact ]
102+ public void GIVEN_InternalAnchorPresentBeforeInitialization_WHEN_InitializeCalled_THEN_AnchorHrefIsCanonicalizedToHashRoute ( )
103+ {
104+ var anchorIndex = _target . AppendAnchor ( "details/ABC?tab=Peers" ) ;
105+
106+ _target . Initialize (
107+ "http://localhost/" ,
108+ "http://localhost/#/" ,
109+ "http://localhost/" ) ;
110+
111+ _target . GetAnchorHref ( anchorIndex ) . Should ( ) . Be ( "http://localhost/#/details/ABC?tab=Peers" ) ;
112+ }
113+
114+ [ Fact ]
115+ public void GIVEN_InternalAnchorAddedAfterInitialization_WHEN_AnchorObserved_THEN_AnchorHrefIsCanonicalizedToHashRoute ( )
116+ {
117+ _target . Initialize (
118+ "http://localhost/proxy/app/" ,
119+ "http://localhost/proxy/app/#/" ,
120+ "http://localhost/proxy/app/" ) ;
121+
122+ var anchorIndex = _target . AppendAnchor ( "settings" ) ;
123+
124+ _target . GetAnchorHref ( anchorIndex ) . Should ( ) . Be ( "http://localhost/proxy/app/#/settings" ) ;
125+ }
126+
127+ [ Fact ]
128+ public void GIVEN_InternalAnchor_WHEN_InitializeCalledWithLinkInterceptionDisabled_THEN_AnchorHrefRemainsPathRoute ( )
129+ {
130+ var anchorIndex = _target . AppendAnchor ( "settings" ) ;
131+
132+ _target . Initialize (
133+ "http://localhost/" ,
134+ "http://localhost/#/" ,
135+ "http://localhost/" ,
136+ new
137+ {
138+ canonicalizeToHash = true ,
139+ hashPrefix = "/" ,
140+ interceptInternalLinks = false
141+ } ) ;
142+
143+ _target . GetAnchorHref ( anchorIndex ) . Should ( ) . Be ( "http://localhost/settings" ) ;
144+ }
145+
101146 private sealed class HashRoutingJavaScriptTestHost
102147 {
103148 private readonly Engine _engine ;
@@ -131,6 +176,20 @@ public string GetLocationHref()
131176 return _engine . Invoke ( "__getLocationHref" ) . AsString ( ) ;
132177 }
133178
179+ public int AppendAnchor ( string href , string ? target = null , bool download = false )
180+ {
181+ var targetValue = target is null
182+ ? JsValue . Null
183+ : JsValue . FromObject ( _engine , target ) ;
184+
185+ return ( int ) _engine . Invoke ( "__appendAnchor" , href , targetValue , download ) . AsNumber ( ) ;
186+ }
187+
188+ public string GetAnchorHref ( int index )
189+ {
190+ return _engine . Invoke ( "__getAnchorHref" , index ) . AsString ( ) ;
191+ }
192+
134193 public string Initialize ( string baseUri , string locationHref , string currentPathUri , object ? options = null )
135194 {
136195 _engine . Invoke ( "__setDocumentBaseUri" , baseUri ) ;
@@ -229,6 +288,86 @@ function URL(raw, base) {
229288function Element() {
230289}
231290
291+ Element.prototype.closest = function(selector) {
292+ return this.matches(selector) ? this : null;
293+ };
294+
295+ Element.prototype.matches = function() {
296+ return false;
297+ };
298+
299+ Element.prototype.querySelectorAll = function() {
300+ return [];
301+ };
302+
303+ function AnchorElement(href, target, download) {
304+ this._attributes = {};
305+
306+ if (href !== null && href !== undefined) {
307+ this._attributes.href = String(href);
308+ }
309+
310+ if (target !== null && target !== undefined) {
311+ this._attributes.target = String(target);
312+ }
313+
314+ if (download) {
315+ this._attributes.download = "";
316+ }
317+ }
318+
319+ AnchorElement.prototype = Object.create(Element.prototype);
320+ AnchorElement.prototype.constructor = AnchorElement;
321+
322+ AnchorElement.prototype.getAttribute = function(name) {
323+ return Object.prototype.hasOwnProperty.call(this._attributes, name)
324+ ? this._attributes[name]
325+ : null;
326+ };
327+
328+ AnchorElement.prototype.setAttribute = function(name, value) {
329+ this._attributes[name] = String(value);
330+
331+ if (document._mutationObserver && name === "href") {
332+ document._mutationObserver._callback([{
333+ type: "attributes",
334+ target: this,
335+ attributeName: "href"
336+ }]);
337+ }
338+ };
339+
340+ AnchorElement.prototype.hasAttribute = function(name) {
341+ return Object.prototype.hasOwnProperty.call(this._attributes, name);
342+ };
343+
344+ AnchorElement.prototype.matches = function(selector) {
345+ return selector === "a[href]" && this.hasAttribute("href");
346+ };
347+
348+ Object.defineProperty(AnchorElement.prototype, "href", {
349+ get: function() {
350+ return new URL(this.getAttribute("href") || "", document.baseURI).href;
351+ },
352+ set: function(value) {
353+ this.setAttribute("href", value);
354+ }
355+ });
356+
357+ function MutationObserver(callback) {
358+ this._callback = callback;
359+ }
360+
361+ MutationObserver.prototype.observe = function() {
362+ document._mutationObserver = this;
363+ };
364+
365+ MutationObserver.prototype.disconnect = function() {
366+ if (document._mutationObserver === this) {
367+ document._mutationObserver = null;
368+ }
369+ };
370+
232371const dotNetObjectReference = {
233372 invokeMethodAsync: function() {
234373 return null;
@@ -238,14 +377,28 @@ function Element() {
238377const document = {
239378 baseURI: "http://localhost/",
240379 _events: {},
380+ _anchors: [],
381+ _mutationObserver: null,
241382 addEventListener: function(name, handler) {
242383 this._events[name] = handler;
243384 },
244385 removeEventListener: function(name) {
245386 delete this._events[name];
387+ },
388+ querySelectorAll: function(selector) {
389+ if (selector !== "a[href]") {
390+ return [];
391+ }
392+
393+ return this._anchors.filter(function(anchor) {
394+ return anchor.matches(selector);
395+ });
246396 }
247397};
248398
399+ document.body = document;
400+ document.documentElement = document;
401+
249402const window = {
250403 _events: {},
251404 _lastReplacedHref: "",
@@ -295,6 +448,25 @@ function __setDocumentBaseUri(value) {
295448 document.baseURI = value;
296449}
297450
451+ function __appendAnchor(href, target, download) {
452+ const anchor = new AnchorElement(href, target, Boolean(download));
453+ document._anchors.push(anchor);
454+
455+ if (document._mutationObserver) {
456+ document._mutationObserver._callback([{
457+ type: "childList",
458+ target: document.body,
459+ addedNodes: [anchor]
460+ }]);
461+ }
462+
463+ return document._anchors.length - 1;
464+ }
465+
466+ function __getAnchorHref(index) {
467+ return document._anchors[index].href;
468+ }
469+
298470function __setLocationAndHistory(href, historyIndex, userState) {
299471 window._lastReplacedHref = "";
300472 window.location.href = href;
0 commit comments