diff --git a/src/constants.js b/src/constants.js index e611b865..77394658 100644 --- a/src/constants.js +++ b/src/constants.js @@ -9,6 +9,15 @@ export const POST_MESSAGE = { ALLOW_DELEGATE: `${ZOID}_allow_delegate`, }; +export const COMPONENT_ERROR = { + NAVIGATED_AWAY: "Window navigated away", + COMPONENT_DESTROYED: "Component destroyed", + COMPONENT_CLOSED: "Component closed", + WINDOW_CLOSED: "Detected component window close", + POPUP_CLOSE: "Detected popup close", + IFRAME_CLOSE: "Detected iframe close", +}; + export const PROP_TYPE = { STRING: ("string": "string"), OBJECT: ("object": "object"), diff --git a/src/parent/parent.js b/src/parent/parent.js index f6fff671..bd92ad29 100644 --- a/src/parent/parent.js +++ b/src/parent/parent.js @@ -65,6 +65,7 @@ import { METHOD, WINDOW_REFERENCE, DEFAULT_DIMENSIONS, + COMPONENT_ERROR, } from "../constants"; import { getGlobal, @@ -729,7 +730,15 @@ export function parentComponent({ return clean.all(err); }) .then(() => { - initPromise.asyncReject(err || new Error("Component destroyed")); + const error = err || new Error(COMPONENT_ERROR.COMPONENT_DESTROYED); + if ( + (currentContainer && isElementClosed(currentContainer)) || + error.message === COMPONENT_ERROR.NAVIGATED_AWAY + ) { + initPromise.resolve(); + } else { + initPromise.asyncReject(error); + } }); }; @@ -749,7 +758,7 @@ export function parentComponent({ return ZalgoPromise.try(() => { return event.trigger(EVENT.CLOSE); }).then(() => { - return destroy(err || new Error(`Component closed`)); + return destroy(err || new Error(COMPONENT_ERROR.COMPONENT_CLOSED)); }); }); }); @@ -823,7 +832,7 @@ export function parentComponent({ window, "unload", once(() => { - destroy(new Error(`Window navigated away`)); + destroy(new Error(COMPONENT_ERROR.NAVIGATED_AWAY)); }) ); @@ -870,7 +879,7 @@ export function parentComponent({ .then((isClosed) => { if (isClosed) { closed = true; - return close(new Error(`Detected component window close`)); + return close(new Error(COMPONENT_ERROR.WINDOW_CLOSED)); } return ZalgoPromise.delay(200) @@ -878,7 +887,7 @@ export function parentComponent({ .then((secondIsClosed) => { if (secondIsClosed) { closed = true; - return close(new Error(`Detected component window close`)); + return close(new Error(COMPONENT_ERROR.WINDOW_CLOSED)); } }); }) diff --git a/test/tests/error.js b/test/tests/error.js index a27acd66..a5535368 100644 --- a/test/tests/error.js +++ b/test/tests/error.js @@ -330,9 +330,9 @@ describe("zoid error cases", () => { ); }); - it("should call onclose when an iframe is closed by someone other than zoid during render", () => { + it("should error out when iframe is closed by someone other than zoid during render", () => { return wrapPromise( - ({ expect, avoid }) => { + ({ expect }) => { window.__component__ = () => { return zoid.create({ tag: "test-onclose-iframe-closed-during-render", @@ -343,17 +343,13 @@ describe("zoid error cases", () => { onWindowOpen().then( expect("onWindowOpen", ({ win: openedWindow }) => { - setTimeout(() => { - destroyElement(openedWindow.frameElement); - }, 1); + destroyElement(openedWindow.frameElement); }) ); const component = window.__component__(); return component({ - onClose: expect("onClose"), - onError: avoid("onError"), - onDestroy: expect("onDestroy"), + onError: expect("onError"), }) .render("body", zoid.CONTEXT.IFRAME) .catch(expect("catch")); @@ -590,25 +586,6 @@ describe("zoid error cases", () => { }); }); - it("should error out where the domain is an invalid regex", () => { - return wrapPromise(({ expect }) => { - window.__component__ = () => { - return zoid.create({ - tag: "test-render-domain-invalid-regex", - url: "mock://www.child.com/base/test/windows/child/index.htm", - domain: /^mock:\/\/www\.meep\.com$/, - }); - }; - - const component = window.__component__(); - return component({ - onDestroy: expect("onDestroy"), - }) - .render(getBody()) - .catch(expect("catch")); - }); - }); - it("should error out where an invalid element is passed", () => { return wrapPromise(({ expect }) => { window.__component__ = () => { diff --git a/test/tests/remove.jsx b/test/tests/remove.jsx index 41c98945..9855b84e 100644 --- a/test/tests/remove.jsx +++ b/test/tests/remove.jsx @@ -102,4 +102,63 @@ describe("zoid remove cases", () => { }); }); }); + + it("should not throw an error when parent container is removed before render completes", () => { + return wrapPromise(() => { + const { container } = getContainer(); + + window.__component__ = () => { + return zoid.create({ + tag: "test-remove-before-render-complete", + url: () => "mock://www.child.com/base/test/windows/child/index.htm", + domain: "mock://www.child.com", + }); + }; + + const component = window.__component__(); + const onRenderedPromise = new ZalgoPromise(); + const onClosePromise = new ZalgoPromise(); + + const renderPromise = component({ + onRendered: () => onRenderedPromise.resolve(), + onClose: () => onClosePromise.resolve(), + }).render(container); + + // manually remove before render completes + container.remove(); + + return renderPromise.then(() => { + ZalgoPromise.delay(5); + }); + }); + }); + + it("should not throw an error when window navigates away before render completes", () => { + return wrapPromise(() => { + const { container } = getContainer(); + + window.__component__ = () => { + return zoid.create({ + tag: "test-navigation-before-render-complete", + url: () => "mock://www.child.com/base/test/windows/child/index.htm", + domain: "mock://www.child.com", + }); + }; + + const component = window.__component__(); + const onRenderedPromise = new ZalgoPromise(); + const onClosePromise = new ZalgoPromise(); + + const renderPromise = component({ + onRendered: () => onRenderedPromise.resolve(), + onClose: () => onClosePromise.resolve(), + }).render(container); + + // manually reload window before render completes + window.dispatchEvent(new Event("unload")); + return renderPromise.then(() => { + ZalgoPromise.delay(5); + }); + }); + }); });