diff --git a/src/sdk/makeNode.js b/src/sdk/makeNode.js index 95b27642..4bf5c1a2 100644 --- a/src/sdk/makeNode.js +++ b/src/sdk/makeNode.js @@ -179,11 +179,33 @@ export default ({ sdk, parent = null, attributes: initialAttributes }) => { onAttributeChange("timezone", tz => updateIntls(tz, getAttribute("locale"))) onAttributeChange("locale", locale => updateIntls(getAttribute("timezone"), locale)) + const pauseReasons = new Set() + const computeAggregatePaused = () => { + const blurReason = !getAttribute("autofetchOnWindowBlur") && getAttribute("blurred") + return Boolean(blurReason) || pauseReasons.size > 0 + } + const applyAggregatePaused = () => { + updateAttribute("paused", computeAggregatePaused()) + } + const addPauseReason = reasonId => { + if (!reasonId) return + pauseReasons.add(reasonId) + applyAggregatePaused() + } + const removePauseReason = reasonId => { + if (!reasonId) return + if (!pauseReasons.delete(reasonId)) return + applyAggregatePaused() + } + onAttributeChange("blurred", applyAggregatePaused) + onAttributeChange("autofetchOnWindowBlur", applyAggregatePaused) + const destroy = () => { if (parent) parent.removeChild(getId()) listeners.offAll() attributeListeners.offAll() + pauseReasons.clear() setTimeout(() => (parent = null), 2000) destroyIntls() } @@ -220,6 +242,8 @@ export default ({ sdk, parent = null, attributes: initialAttributes }) => { formatTime, formatDate, formatXAxis, + addPauseReason, + removePauseReason, } return instance diff --git a/src/sdk/makeNode.test.js b/src/sdk/makeNode.test.js index efbaef05..fb227d87 100644 --- a/src/sdk/makeNode.test.js +++ b/src/sdk/makeNode.test.js @@ -238,6 +238,72 @@ describe("makeNode", () => { }) }) + describe("pause reasons", () => { + it("starts with no reasons and paused=false (assuming blur is off)", () => { + expect(node.getAttribute("paused")).toBeUndefined() + }) + + it("addPauseReason flips paused to true", () => { + node.addPauseReason("hover") + expect(node.getAttribute("paused")).toBe(true) + }) + + it("removePauseReason after the only reason flips paused back to false", () => { + node.addPauseReason("hover") + node.removePauseReason("hover") + expect(node.getAttribute("paused")).toBe(false) + }) + + it("composes multiple reasons — removing one keeps paused true while others remain", () => { + node.addPauseReason("hover") + node.addPauseReason("modal") + expect(node.getAttribute("paused")).toBe(true) + node.removePauseReason("hover") + expect(node.getAttribute("paused")).toBe(true) + node.removePauseReason("modal") + expect(node.getAttribute("paused")).toBe(false) + }) + + it("is idempotent — same reason id added twice acts as one", () => { + node.addPauseReason("hover") + node.addPauseReason("hover") + node.removePauseReason("hover") + expect(node.getAttribute("paused")).toBe(false) + }) + + it("ignores empty/missing reason ids", () => { + node.addPauseReason("") + node.addPauseReason(null) + node.addPauseReason(undefined) + expect(node.getAttribute("paused")).toBeUndefined() + }) + + it("recomputes when the blur attributes flip — window blur fires pause without an explicit reason", () => { + node.updateAttribute("autofetchOnWindowBlur", false) + node.updateAttribute("blurred", true) + expect(node.getAttribute("paused")).toBe(true) + node.updateAttribute("blurred", false) + expect(node.getAttribute("paused")).toBe(false) + }) + + it("blur and explicit reasons combine — un-blurring keeps paused true if a reason is still registered", () => { + node.updateAttribute("autofetchOnWindowBlur", false) + node.updateAttribute("blurred", true) + node.addPauseReason("modal") + expect(node.getAttribute("paused")).toBe(true) + node.updateAttribute("blurred", false) + expect(node.getAttribute("paused")).toBe(true) + node.removePauseReason("modal") + expect(node.getAttribute("paused")).toBe(false) + }) + + it("autofetchOnWindowBlur=true disables the blur reason — paused stays false while blurred", () => { + node.updateAttribute("autofetchOnWindowBlur", true) + node.updateAttribute("blurred", true) + expect(node.getAttribute("paused")).toBe(false) + }) + }) + describe("inheritance", () => { it("inherits parent attributes", () => { mockParent.getAttributes.mockReturnValue({