diff --git a/src/loadScript.ts b/src/loadScript.ts index 3e77e6e97..022fc7934 100644 --- a/src/loadScript.ts +++ b/src/loadScript.ts @@ -42,6 +42,15 @@ export const loadScript = ( loaded = true; runCallbacks(); }; + embedScript.onerror = () => { + // Remove the failed script tag so a later loadScript call (e.g. a + // remount) is able to inject and retry it; otherwise isScriptInjected + // keeps returning true and the editor can never load on this page. + embedScript.remove(); + console.error( + `react-email-editor: failed to load unlayer embed script from ${scriptUrl}` + ); + }; document.head.appendChild(embedScript); } else { runCallbacks(); diff --git a/test/loadScript.test.ts b/test/loadScript.test.ts new file mode 100644 index 000000000..aab45fdf7 --- /dev/null +++ b/test/loadScript.test.ts @@ -0,0 +1,31 @@ +import { loadScript } from '../src/loadScript'; + +const scriptUrl = 'https://example.com/test-embed.js'; + +const getScript = () => + document.querySelector(`script[src="${scriptUrl}"]`) as HTMLScriptElement | null; + +it('removes a failed script tag so a later call can retry', () => { + jest.spyOn(console, 'error').mockImplementation(() => {}); + const callback = jest.fn(); + + loadScript(callback, scriptUrl); + const firstScript = getScript(); + expect(firstScript).toBeTruthy(); + + // Simulate a network failure loading the embed script. + firstScript!.dispatchEvent(new Event('error')); + + expect(getScript()).toBeNull(); + expect(callback).not.toHaveBeenCalled(); + + // A subsequent call (e.g. a remount) injects the script again. + loadScript(callback, scriptUrl); + const secondScript = getScript(); + expect(secondScript).toBeTruthy(); + + secondScript!.dispatchEvent(new Event('load')); + + // Both queued callbacks run once the script finally loads. + expect(callback).toHaveBeenCalledTimes(2); +});