diff --git a/src/utils/IterableActionRunner.test.ts b/src/utils/IterableActionRunner.test.ts new file mode 100644 index 00000000..566867f2 --- /dev/null +++ b/src/utils/IterableActionRunner.test.ts @@ -0,0 +1,98 @@ +import { IterableActionRunner } from './IterableActionRunner'; +import { IterableConfig } from './IterableConfig'; +import { IterableActionSource } from '../embedded/types'; + +describe('IterableActionRunner', () => { + const openAction = { type: 'openUrl', data: 'https://example.com' }; + + beforeEach(() => { + IterableConfig.urlHandler = null; + IterableConfig.customActionHandler = null; + IterableConfig.openLinksInNewTab = true; + jest.restoreAllMocks(); + }); + + it('should return false when action is null', () => { + const result = IterableActionRunner.executeAction( + null, + null, + IterableActionSource.EMBEDDED + ); + expect(result).toBe(false); + }); + + it('should open URL in new tab by default', () => { + const openSpy = jest.spyOn(window, 'open').mockImplementation(); + + IterableActionRunner.executeAction( + null, + openAction, + IterableActionSource.EMBEDDED + ); + + expect(openSpy).toHaveBeenCalledWith('https://example.com', '_blank'); + }); + + it('should open URL in same tab when openLinksInNewTab is false', () => { + IterableConfig.openLinksInNewTab = false; + const assignMock = jest.fn(); + Object.defineProperty(window, 'location', { + value: { assign: assignMock }, + writable: true + }); + + IterableActionRunner.executeAction( + null, + openAction, + IterableActionSource.EMBEDDED + ); + + expect(assignMock).toHaveBeenCalledWith('https://example.com'); + }); + + it('should use urlHandler when configured', () => { + const handleIterableURL = jest.fn().mockReturnValue(true); + IterableConfig.urlHandler = { handleIterableURL }; + + const result = IterableActionRunner.executeAction( + null, + openAction, + IterableActionSource.EMBEDDED + ); + + expect(result).toBe(true); + expect(handleIterableURL).toHaveBeenCalledWith( + 'https://example.com', + expect.objectContaining({ action: openAction }) + ); + }); + + it('should fall back to default open when urlHandler returns false', () => { + const handleIterableURL = jest.fn().mockReturnValue(false); + IterableConfig.urlHandler = { handleIterableURL }; + const openSpy = jest.spyOn(window, 'open').mockImplementation(); + + IterableActionRunner.executeAction( + null, + openAction, + IterableActionSource.EMBEDDED + ); + + expect(openSpy).toHaveBeenCalledWith('https://example.com', '_blank'); + }); + + it('should call customActionHandler for non-URL actions', () => { + const handleIterableCustomAction = jest.fn().mockReturnValue(true); + IterableConfig.customActionHandler = { handleIterableCustomAction }; + + const customAction = { type: 'customType', data: 'someData' }; + const result = IterableActionRunner.executeAction( + null, + customAction, + IterableActionSource.EMBEDDED + ); + + expect(result).toBe(true); + expect(handleIterableCustomAction).toHaveBeenCalled(); + }); +}); diff --git a/src/utils/IterableActionRunner.ts b/src/utils/IterableActionRunner.ts index 67bd9326..a2fc3163 100644 --- a/src/utils/IterableActionRunner.ts +++ b/src/utils/IterableActionRunner.ts @@ -36,7 +36,11 @@ class IterableActionRunnerImpl { } } - window.open(uri, '_blank'); + if (IterableConfig.openLinksInNewTab) { + window.open(uri, '_blank'); + } else { + window.location.assign(uri); + } return true; } diff --git a/src/utils/IterableConfig.ts b/src/utils/IterableConfig.ts index 70185a51..ea50d2c7 100644 --- a/src/utils/IterableConfig.ts +++ b/src/utils/IterableConfig.ts @@ -4,4 +4,11 @@ export class IterableConfig { public static urlHandler: IterableUrlHandler | null = null; public static customActionHandler: IterableCustomActionHandler | null = null; + + /** + * Controls whether URLs are opened in a new tab/window or the same tab. + * When true (default), URLs open in a new tab via window.open(url, '_blank'). + * When false, URLs open in the same tab via window.location.assign(url). + */ + public static openLinksInNewTab = true; }