From dc335e88c9dee4f6cfd60f41bb99f39bcf37c3a6 Mon Sep 17 00:00:00 2001 From: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Date: Wed, 28 May 2025 15:15:05 +0530 Subject: [PATCH] fix: External links in widget not opening in new tab (#11608) --- app/javascript/portal/portalHelpers.js | 11 +-- app/javascript/portal/specs/portal.spec.js | 98 +++++++++++++++++++++- 2 files changed, 99 insertions(+), 10 deletions(-) diff --git a/app/javascript/portal/portalHelpers.js b/app/javascript/portal/portalHelpers.js index 5cced0fa4..9cb28e63a 100644 --- a/app/javascript/portal/portalHelpers.js +++ b/app/javascript/portal/portalHelpers.js @@ -38,16 +38,9 @@ export const openExternalLinksInNewTab = () => { document.addEventListener('click', event => { if (!isOnArticlePage) return; - // Some of the links come wrapped in strong tag through prosemirror - - const isTagAnchor = event.target.tagName === 'A'; - const isParentTagAnchor = - event.target.tagName === 'STRONG' && - event.target.parentNode.tagName === 'A'; - - if (isTagAnchor || isParentTagAnchor) { - const link = isTagAnchor ? event.target : event.target.parentNode; + const link = event.target.closest('a'); + if (link) { const isInternalLink = link.hostname === window.location.hostname || link.href.includes(customDomain) || diff --git a/app/javascript/portal/specs/portal.spec.js b/app/javascript/portal/specs/portal.spec.js index 13edd3718..861950a57 100644 --- a/app/javascript/portal/specs/portal.spec.js +++ b/app/javascript/portal/specs/portal.spec.js @@ -1,6 +1,9 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { JSDOM } from 'jsdom'; -import { InitializationHelpers } from '../portalHelpers'; +import { + InitializationHelpers, + openExternalLinksInNewTab, +} from '../portalHelpers'; describe('InitializationHelpers.navigateToLocalePage', () => { let dom; @@ -44,3 +47,96 @@ describe('InitializationHelpers.navigateToLocalePage', () => { ); }); }); + +describe('openExternalLinksInNewTab', () => { + let dom; + let document; + let window; + + beforeEach(() => { + dom = new JSDOM( + ` + + +
+ External + Internal + Custom + CodeBold + +
+ + `, + { url: 'https://app.chatwoot.com/hc/article' } + ); + + document = dom.window.document; + window = dom.window; + + window.portalConfig = { + customDomain: 'custom.domain.com', + hostURL: 'app.chatwoot.com', + }; + + global.document = document; + global.window = window; + }); + + afterEach(() => { + dom = null; + document = null; + window = null; + delete global.document; + delete global.window; + }); + + const simulateClick = selector => { + const element = document.querySelector(selector); + const event = new window.MouseEvent('click', { bubbles: true }); + element.dispatchEvent(event); + return element.closest('a') || element; + }; + + it('opens external links in new tab', () => { + openExternalLinksInNewTab(); + + const link = simulateClick('#external'); + + expect(link.target).toBe('_blank'); + expect(link.rel).toBe('noopener noreferrer'); + }); + + it('preserves internal links', () => { + openExternalLinksInNewTab(); + + const internal = simulateClick('#internal'); + const custom = simulateClick('#custom'); + + expect(internal.target).not.toBe('_blank'); + expect(custom.target).not.toBe('_blank'); + }); + + it('handles clicks on nested elements', () => { + openExternalLinksInNewTab(); + + simulateClick('#nested code'); + simulateClick('#nested strong'); + + const link = document.getElementById('nested'); + expect(link.target).toBe('_blank'); + expect(link.rel).toBe('noopener noreferrer'); + }); + + it('handles links inside list items with strong tags', () => { + openExternalLinksInNewTab(); + + // Click on the strong element inside the link in the list + simulateClick('#list-link strong'); + + const link = document.getElementById('list-link'); + expect(link.target).toBe('_blank'); + expect(link.rel).toBe('noopener noreferrer'); + }); +});