mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-11-03 20:48:07 +00:00 
			
		
		
		
	fix: External links in widget not opening in new tab (#11608)
This commit is contained in:
		@@ -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) ||
 | 
			
		||||
 
 | 
			
		||||
@@ -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(
 | 
			
		||||
      `<!DOCTYPE html>
 | 
			
		||||
      <html>
 | 
			
		||||
        <body>
 | 
			
		||||
          <div id="cw-article-content">
 | 
			
		||||
            <a href="https://external.com" id="external">External</a>
 | 
			
		||||
            <a href="https://app.chatwoot.com/page" id="internal">Internal</a>
 | 
			
		||||
            <a href="https://custom.domain.com/page" id="custom">Custom</a>
 | 
			
		||||
            <a href="https://example.com" id="nested"><code>Code</code><strong>Bold</strong></a>
 | 
			
		||||
            <ul>
 | 
			
		||||
              <li>Visit the preferences centre here > <a href="https://external.com" id="list-link"><strong>https://external.com</strong></a></li>
 | 
			
		||||
            </ul>
 | 
			
		||||
          </div>
 | 
			
		||||
        </body>
 | 
			
		||||
      </html>`,
 | 
			
		||||
      { 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');
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user