@ably/ui 18.1.0 → 18.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -2
- package/core/Code.js.map +1 -1
- package/core/Flash.js.map +1 -1
- package/core/Loader.js.map +1 -1
- package/core/Notice.js.map +1 -1
- package/core/SessionData.js.map +1 -1
- package/core/hooks/use-themed-scrollpoints.test.js +1 -1
- package/core/hooks/use-themed-scrollpoints.test.js.map +1 -1
- package/core/insights/command-queue.js +1 -1
- package/core/insights/command-queue.js.map +1 -1
- package/core/insights/index.js +1 -1
- package/core/insights/index.js.map +1 -1
- package/core/insights/index.test.js +1 -1
- package/core/insights/index.test.js.map +1 -1
- package/core/insights/mixpanel.js +1 -1
- package/core/insights/mixpanel.js.map +1 -1
- package/core/insights/mixpanel.test.js +1 -1
- package/core/insights/mixpanel.test.js.map +1 -1
- package/core/insights/posthog.js +1 -1
- package/core/insights/posthog.js.map +1 -1
- package/core/insights/posthog.test.js +1 -1
- package/core/insights/posthog.test.js.map +1 -1
- package/core/insights/service.js +1 -1
- package/core/insights/service.js.map +1 -1
- package/core/insights/types.js.map +1 -1
- package/core/utils/sanitize-html.js +2 -0
- package/core/utils/sanitize-html.js.map +1 -0
- package/core/utils/sanitize-html.test.js +2 -0
- package/core/utils/sanitize-html.test.js.map +1 -0
- package/index.d.ts +48 -68
- package/package.json +12 -11
package/AGENTS.md
CHANGED
|
@@ -25,13 +25,13 @@
|
|
|
25
25
|
& tailwind-merge)
|
|
26
26
|
- **Formatting**: Prettier defaults (no config = defaults), 2-space indent
|
|
27
27
|
- **Error Handling**: Wrap external service calls in try-catch, log with logger module
|
|
28
|
-
- **Comments**: JSDoc for props, inline comments for complex logic
|
|
28
|
+
- **Comments**: JSDoc for props, brief inline comments only for genuinely complex logic — keep them terse per the [root AGENTS.md](../../AGENTS.md) comment rule
|
|
29
29
|
|
|
30
30
|
### Custom Hooks
|
|
31
31
|
|
|
32
32
|
- **Naming**: `use` prefix with descriptive name (e.g., `useContentHeight`, `useThemedScrollpoints`)
|
|
33
33
|
- **JSDoc**: Always include for custom hooks, especially performance-related ones — document `@param` with types/defaults and `@returns` with type and semantic meaning
|
|
34
|
-
- **Performance rationale**:
|
|
34
|
+
- **Performance rationale**: Note the "why" in JSDoc when optimizing — a short phrase, not a paragraph (e.g., "eliminates forced reflows")
|
|
35
35
|
- **Cleanup**: Always return cleanup function to prevent memory leaks
|
|
36
36
|
- **Shared constants**: Import from `src/core/utils/heights.ts` instead of duplicating
|
|
37
37
|
|
package/core/Code.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/Code.tsx"],"sourcesContent":["import React from \"react\";\nimport {\n highlightSnippet,\n LINE_HIGHLIGHT_CLASSES,\n registerDefaultLanguages,\n splitHtmlLines,\n} from \"./utils/syntax-highlighter\";\nimport languagesRegistry from \"./utils/syntax-highlighter-registry\";\nimport cn from \"./utils/cn\";\n\nregisterDefaultLanguages(languagesRegistry);\n\nexport type LineHighlightType = \"addition\" | \"removal\" | \"highlight\";\n\ntype CodeProps = {\n language: string;\n snippet: string;\n textSize?: string;\n padding?: string;\n additionalCSS?: string;\n showLines?: boolean;\n lineCSS?: string;\n wrap?: boolean;\n lineHighlights?: Record<number, LineHighlightType>;\n};\n\nconst Code = ({\n language,\n snippet,\n textSize = \"ui-text-code\",\n padding = \"p-8\",\n additionalCSS = \"\",\n showLines,\n lineCSS,\n wrap = false,\n lineHighlights,\n}: CodeProps) => {\n // Trim the snippet and remove trailing empty lines\n const trimmedSnippet = snippet.trimEnd();\n const HTMLraw = highlightSnippet(language, trimmedSnippet) ?? \"\";\n const className = `language-${language} ${textSize}`;\n\n // Calculate line count after removing trailing empty lines\n const lines = trimmedSnippet.split(/\\r\\n|\\r|\\n/);\n const lineCount = lines.length;\n\n const hasHighlights =\n lineHighlights && Object.keys(lineHighlights).length > 0;\n\n // Per-line rendering when highlights are present\n if (hasHighlights) {\n const htmlLines = splitHtmlLines(HTMLraw);\n\n return (\n <div\n className={cn(\"hljs overflow-y-auto\", padding, additionalCSS)}\n data-id=\"code\"\n >\n <pre\n lang={language}\n className={cn(\n \"h-full flex-1 text-p4 leading-normal\",\n wrap ? \"whitespace-pre-wrap break-words\" : \"overflow-x-auto\",\n )}\n >\n <code className={className}>\n {htmlLines.map((lineHtml, i) => {\n const lineNum = i + 1;\n const highlightType = lineHighlights[lineNum];\n const highlightClass = highlightType\n ? LINE_HIGHLIGHT_CLASSES[highlightType]\n : undefined;\n\n return (\n <span\n key={i}\n className={cn(\"flex min-w-full pl-2\", highlightClass)}\n >\n {showLines && (\n <span\n className={cn(\n \"mr-4 font-mono text-right text-neutral-800 select-none shrink-0 inline-block leading-normal\",\n lineCSS,\n )}\n style={{ minWidth: `${String(lineCount).length}ch` }}\n >\n {lineNum}\n </span>\n )}\n <span\n className=\"flex-1 !leading-normal\"\n dangerouslySetInnerHTML={{\n __html: lineHtml || \" \",\n }}\n />\n </span>\n );\n })}\n </code>\n </pre>\n </div>\n );\n }\n\n // Default: single-block rendering (no highlights)\n return (\n <div\n className={cn(\"hljs overflow-y-auto flex\", padding, additionalCSS)}\n data-id=\"code\"\n >\n {showLines ? (\n <div className=\"text-p4 leading-normal pt-px\">\n {[...Array(lineCount)].map((_, i) => (\n <p\n className={cn(\n \"mr-4 font-mono text-right text-neutral-800\",\n lineCSS,\n )}\n key={i}\n >\n {i + 1}\n </p>\n ))}\n </div>\n ) : null}\n <pre\n lang={language}\n className={cn(\n \"h-full flex-1 text-p4 leading-normal\",\n wrap ? \"whitespace-pre-wrap break-words\" : \"overflow-x-auto\",\n )}\n >\n <code\n className={className}\n dangerouslySetInnerHTML={{ __html: HTMLraw }}\n />\n </pre>\n </div>\n );\n};\n\nexport default Code;\n"],"names":["React","highlightSnippet","LINE_HIGHLIGHT_CLASSES","registerDefaultLanguages","splitHtmlLines","languagesRegistry","cn","Code","language","snippet","textSize","padding","additionalCSS","showLines","lineCSS","wrap","lineHighlights","trimmedSnippet","trimEnd","HTMLraw","className","lines","split","lineCount","length","hasHighlights","Object","keys","htmlLines","div","data-id","pre","lang","code","map","lineHtml","i","lineNum","highlightType","highlightClass","undefined","span","key","style","minWidth","String","dangerouslySetInnerHTML","__html","Array","_","p"],"mappings":"AAAA,OAAOA,UAAW,OAAQ,AAC1B,QACEC,gBAAgB,CAChBC,sBAAsB,CACtBC,wBAAwB,CACxBC,cAAc,KACT,4BAA6B,AACpC,QAAOC,sBAAuB,qCAAsC,AACpE,QAAOC,OAAQ,YAAa,CAE5BH,yBAAyBE,mBAgBzB,MAAME,KAAO,CAAC,CACZC,QAAQ,CACRC,OAAO,CACPC,SAAW,cAAc,CACzBC,QAAU,KAAK,CACfC,cAAgB,EAAE,CAClBC,SAAS,CACTC,OAAO,CACPC,KAAO,KAAK,CACZC,cAAc,CACJ,IAEV,MAAMC,eAAiBR,QAAQS,OAAO,GACtC,MAAMC,QAAUlB,iBAAiBO,SAAUS,iBAAmB,GAC9D,MAAMG,UAAY,CAAC,SAAS,EAAEZ,SAAS,CAAC,EAAEE,SAAS,CAAC,CAGpD,MAAMW,MAAQJ,eAAeK,KAAK,CAAC,cACnC,MAAMC,UAAYF,MAAMG,MAAM,CAE9B,MAAMC,cACJT,gBAAkBU,OAAOC,IAAI,CAACX,gBAAgBQ,MAAM,CAAG,EAGzD,GAAIC,cAAe,CACjB,MAAMG,UAAYxB,eAAee,SAEjC,OACE,oBAACU,OACCT,UAAWd,GAAG,uBAAwBK,QAASC,eAC/CkB,UAAQ,QAER,oBAACC,OACCC,KAAMxB,SACNY,UAAWd,GACT,uCACAS,KAAO,kCAAoC,oBAG7C,oBAACkB,QAAKb,UAAWA,WACdQ,UAAUM,GAAG,CAAC,CAACC,SAAUC,KACxB,MAAMC,QAAUD,EAAI,EACpB,MAAME,cAAgBtB,cAAc,CAACqB,QAAQ,CAC7C,MAAME,eAAiBD,cACnBpC,sBAAsB,CAACoC,cAAc,CACrCE,UAEJ,OACE,oBAACC,QACCC,IAAKN,EACLhB,UAAWd,GAAG,uBAAwBiC,iBAErC1B,WACC,oBAAC4B,QACCrB,UAAWd,GACT,8FACAQ,SAEF6B,MAAO,CAAEC,SAAU,CAAC,EAAEC,OAAOtB,WAAWC,MAAM,CAAC,EAAE,CAAC,AAAC,GAElDa,SAGL,oBAACI,QACCrB,UAAU,
|
|
1
|
+
{"version":3,"sources":["../../src/core/Code.tsx"],"sourcesContent":["import React from \"react\";\nimport {\n highlightSnippet,\n LINE_HIGHLIGHT_CLASSES,\n registerDefaultLanguages,\n splitHtmlLines,\n} from \"./utils/syntax-highlighter\";\nimport languagesRegistry from \"./utils/syntax-highlighter-registry\";\nimport cn from \"./utils/cn\";\n\nregisterDefaultLanguages(languagesRegistry);\n\nexport type LineHighlightType = \"addition\" | \"removal\" | \"highlight\";\n\ntype CodeProps = {\n language: string;\n snippet: string;\n textSize?: string;\n padding?: string;\n additionalCSS?: string;\n showLines?: boolean;\n lineCSS?: string;\n wrap?: boolean;\n lineHighlights?: Record<number, LineHighlightType>;\n};\n\nconst Code = ({\n language,\n snippet,\n textSize = \"ui-text-code\",\n padding = \"p-8\",\n additionalCSS = \"\",\n showLines,\n lineCSS,\n wrap = false,\n lineHighlights,\n}: CodeProps) => {\n // Trim the snippet and remove trailing empty lines\n const trimmedSnippet = snippet.trimEnd();\n const HTMLraw = highlightSnippet(language, trimmedSnippet) ?? \"\";\n const className = `language-${language} ${textSize}`;\n\n // Calculate line count after removing trailing empty lines\n const lines = trimmedSnippet.split(/\\r\\n|\\r|\\n/);\n const lineCount = lines.length;\n\n const hasHighlights =\n lineHighlights && Object.keys(lineHighlights).length > 0;\n\n // Per-line rendering when highlights are present\n if (hasHighlights) {\n const htmlLines = splitHtmlLines(HTMLraw);\n\n return (\n <div\n className={cn(\"hljs overflow-y-auto\", padding, additionalCSS)}\n data-id=\"code\"\n >\n <pre\n lang={language}\n className={cn(\n \"h-full flex-1 text-p4 leading-normal\",\n wrap ? \"whitespace-pre-wrap break-words\" : \"overflow-x-auto\",\n )}\n >\n <code className={className}>\n {htmlLines.map((lineHtml, i) => {\n const lineNum = i + 1;\n const highlightType = lineHighlights[lineNum];\n const highlightClass = highlightType\n ? LINE_HIGHLIGHT_CLASSES[highlightType]\n : undefined;\n\n return (\n <span\n key={i}\n className={cn(\"flex min-w-full pl-2\", highlightClass)}\n >\n {showLines && (\n <span\n className={cn(\n \"mr-4 font-mono text-right text-neutral-800 select-none shrink-0 inline-block leading-normal\",\n lineCSS,\n )}\n style={{ minWidth: `${String(lineCount).length}ch` }}\n >\n {lineNum}\n </span>\n )}\n <span\n className=\"flex-1 !leading-normal\"\n // lineHtml comes from highlightSnippet() which HTML-escapes\n // its input before emitting tokens. The fallback is the\n // literal \" \" entity. No caller-controlled HTML reaches\n // this sink.\n // eslint-disable-next-line react/no-danger\n dangerouslySetInnerHTML={{\n __html: lineHtml || \" \",\n }}\n />\n </span>\n );\n })}\n </code>\n </pre>\n </div>\n );\n }\n\n // Default: single-block rendering (no highlights)\n return (\n <div\n className={cn(\"hljs overflow-y-auto flex\", padding, additionalCSS)}\n data-id=\"code\"\n >\n {showLines ? (\n <div className=\"text-p4 leading-normal pt-px\">\n {[...Array(lineCount)].map((_, i) => (\n <p\n className={cn(\n \"mr-4 font-mono text-right text-neutral-800\",\n lineCSS,\n )}\n key={i}\n >\n {i + 1}\n </p>\n ))}\n </div>\n ) : null}\n <pre\n lang={language}\n className={cn(\n \"h-full flex-1 text-p4 leading-normal\",\n wrap ? \"whitespace-pre-wrap break-words\" : \"overflow-x-auto\",\n )}\n >\n <code\n className={className}\n // HTMLraw is produced by highlightSnippet(); same trust analysis as\n // the lineHtml site above — the highlighter escapes input before\n // emitting tokens.\n // eslint-disable-next-line react/no-danger\n dangerouslySetInnerHTML={{ __html: HTMLraw }}\n />\n </pre>\n </div>\n );\n};\n\nexport default Code;\n"],"names":["React","highlightSnippet","LINE_HIGHLIGHT_CLASSES","registerDefaultLanguages","splitHtmlLines","languagesRegistry","cn","Code","language","snippet","textSize","padding","additionalCSS","showLines","lineCSS","wrap","lineHighlights","trimmedSnippet","trimEnd","HTMLraw","className","lines","split","lineCount","length","hasHighlights","Object","keys","htmlLines","div","data-id","pre","lang","code","map","lineHtml","i","lineNum","highlightType","highlightClass","undefined","span","key","style","minWidth","String","dangerouslySetInnerHTML","__html","Array","_","p"],"mappings":"AAAA,OAAOA,UAAW,OAAQ,AAC1B,QACEC,gBAAgB,CAChBC,sBAAsB,CACtBC,wBAAwB,CACxBC,cAAc,KACT,4BAA6B,AACpC,QAAOC,sBAAuB,qCAAsC,AACpE,QAAOC,OAAQ,YAAa,CAE5BH,yBAAyBE,mBAgBzB,MAAME,KAAO,CAAC,CACZC,QAAQ,CACRC,OAAO,CACPC,SAAW,cAAc,CACzBC,QAAU,KAAK,CACfC,cAAgB,EAAE,CAClBC,SAAS,CACTC,OAAO,CACPC,KAAO,KAAK,CACZC,cAAc,CACJ,IAEV,MAAMC,eAAiBR,QAAQS,OAAO,GACtC,MAAMC,QAAUlB,iBAAiBO,SAAUS,iBAAmB,GAC9D,MAAMG,UAAY,CAAC,SAAS,EAAEZ,SAAS,CAAC,EAAEE,SAAS,CAAC,CAGpD,MAAMW,MAAQJ,eAAeK,KAAK,CAAC,cACnC,MAAMC,UAAYF,MAAMG,MAAM,CAE9B,MAAMC,cACJT,gBAAkBU,OAAOC,IAAI,CAACX,gBAAgBQ,MAAM,CAAG,EAGzD,GAAIC,cAAe,CACjB,MAAMG,UAAYxB,eAAee,SAEjC,OACE,oBAACU,OACCT,UAAWd,GAAG,uBAAwBK,QAASC,eAC/CkB,UAAQ,QAER,oBAACC,OACCC,KAAMxB,SACNY,UAAWd,GACT,uCACAS,KAAO,kCAAoC,oBAG7C,oBAACkB,QAAKb,UAAWA,WACdQ,UAAUM,GAAG,CAAC,CAACC,SAAUC,KACxB,MAAMC,QAAUD,EAAI,EACpB,MAAME,cAAgBtB,cAAc,CAACqB,QAAQ,CAC7C,MAAME,eAAiBD,cACnBpC,sBAAsB,CAACoC,cAAc,CACrCE,UAEJ,OACE,oBAACC,QACCC,IAAKN,EACLhB,UAAWd,GAAG,uBAAwBiC,iBAErC1B,WACC,oBAAC4B,QACCrB,UAAWd,GACT,8FACAQ,SAEF6B,MAAO,CAAEC,SAAU,CAAC,EAAEC,OAAOtB,WAAWC,MAAM,CAAC,EAAE,CAAC,AAAC,GAElDa,SAGL,oBAACI,QACCrB,UAAU,yBAMV0B,wBAAyB,CACvBC,OAAQZ,UAAY,QACtB,IAIR,KAKV,CAGA,OACE,oBAACN,OACCT,UAAWd,GAAG,4BAA6BK,QAASC,eACpDkB,UAAQ,QAEPjB,UACC,oBAACgB,OAAIT,UAAU,gCACZ,IAAI4B,MAAMzB,WAAW,CAACW,GAAG,CAAC,CAACe,EAAGb,IAC7B,oBAACc,KACC9B,UAAWd,GACT,6CACAQ,SAEF4B,IAAKN,GAEJA,EAAI,KAIT,KACJ,oBAACL,OACCC,KAAMxB,SACNY,UAAWd,GACT,uCACAS,KAAO,kCAAoC,oBAG7C,oBAACkB,QACCb,UAAWA,UAKX0B,wBAAyB,CAAEC,OAAQ5B,OAAQ,KAKrD,CAEA,gBAAeZ,IAAK"}
|
package/core/Flash.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/Flash.tsx"],"sourcesContent":["import React, {\n useEffect,\n useState,\n useRef,\n createContext,\n useContext,\n useCallback,\n useMemo,\n PropsWithChildren,\n} from \"react\";\nimport DOMPurify from \"dompurify\";\nimport Icon from \"./Icon\";\nimport { ColorClass } from \"./styles/colors/types\";\nimport { IconName } from \"./Icon/types\";\n\ntype FlashPropsType = \"error\" | \"success\" | \"notice\" | \"info\" | \"alert\";\n\ntype FlashProps = {\n id: string;\n removed: boolean;\n type: FlashPropsType;\n content: string;\n removeFlash: (id: string) => void;\n};\n\ntype BackendFlashesProps = {\n flashes: string[][];\n};\n\nconst FLASH_DATA_ID = \"ui-flashes\";\n\ntype FlashContextType = {\n flashes: FlashProps[];\n addFlashes: (flashes: Pick<FlashProps, \"type\" | \"content\">[]) => void;\n removeFlash: (id: string) => void;\n};\n\nconst FlashContext = createContext<FlashContextType | undefined>(undefined);\n\ntype FlashProviderProps = PropsWithChildren;\n\n/**\n * FlashProvider - Context provider for managing flash messages throughout the application.\n *\n * Maintains a global list of flash messages and provides methods to add/remove them.\n * Automatically deduplicates messages with the same type and content to prevent duplicates.\n * Use this at the app root level and access via useFlashContext() in child components.\n */\nconst FlashProvider = ({ children }: FlashProviderProps) => {\n const [flashes, setFlashes] = useState<FlashProps[]>([]);\n\n const removeFlash = useCallback((flashId: string) => {\n setFlashes((prev) => prev.filter((item) => item.id !== flashId));\n }, []);\n\n const addFlashes = useCallback(\n (newFlashes: Pick<FlashProps, \"type\" | \"content\">[]) => {\n setFlashes((prev) => {\n const withIds = newFlashes\n .filter(\n (flash) =>\n !prev.some(\n (existing) =>\n existing.content === flash.content &&\n existing.type === flash.type,\n ),\n )\n .map((flash) => ({\n ...flash,\n id: Math.random().toString(36).slice(2),\n removed: false,\n removeFlash,\n }));\n\n return [...prev, ...withIds];\n });\n },\n [removeFlash],\n );\n\n const contextValue = useMemo(\n () => ({ flashes, addFlashes, removeFlash }),\n [flashes, addFlashes, removeFlash],\n );\n\n return (\n <FlashContext.Provider value={contextValue}>\n {children}\n </FlashContext.Provider>\n );\n};\n\nconst useFlashContext = () => {\n const context = useContext(FlashContext);\n if (context === undefined) {\n throw new Error(\"useFlashContext must be used within FlashProvider\");\n }\n return context;\n};\n\nconst FLASH_BG_COLOR = {\n error: \"bg-gui-error\",\n success: \"bg-zingy-green\",\n notice: \"bg-electric-cyan\",\n info: \"bg-electric-cyan\",\n alert: \"bg-active-orange\",\n};\n\nconst FLASH_TEXT_COLOR = {\n error: \"text-white\",\n success: \"text-cool-black\",\n notice: \"text-cool-black\",\n info: \"text-cool-black\",\n alert: \"text-white\",\n};\n\nconst AUTO_HIDE = [\"success\", \"info\", \"notice\"];\nconst AUTO_HIDE_TIME = 8000;\n\nconst useAutoHide = (type: string, closeFlash: () => void) => {\n const timeoutId = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n if (AUTO_HIDE.includes(type)) {\n timeoutId.current = setTimeout(() => {\n closeFlash();\n }, AUTO_HIDE_TIME);\n }\n\n return () => {\n if (timeoutId.current) {\n clearTimeout(timeoutId.current);\n }\n };\n }, [type, closeFlash]);\n};\n\n/**\n * Flash - Individual flash message component with animations and auto-dismiss.\n *\n * Displays a colored notification banner with an icon, message content, and close button.\n * Success/info/notice messages auto-hide after 8 seconds. Error/alert messages require\n * manual dismissal. Uses CSS animations for smooth entry/exit and height transitions\n * on close. Content is sanitized with DOMPurify to allow safe HTML links from backend.\n */\nconst Flash = ({ id, type, content, removeFlash }: FlashProps) => {\n const ref = useRef<HTMLDivElement>(null);\n const [closed, setClosed] = useState(false);\n const [flashHeight, setFlashHeight] = useState(0);\n\n const closeFlash = () => {\n if (ref.current) {\n setFlashHeight(ref.current.getBoundingClientRect().height);\n }\n\n setClosed(true);\n\n setTimeout(() => {\n if (id) {\n removeFlash(id);\n }\n }, 100);\n };\n\n useAutoHide(type, closeFlash);\n\n const animateEntry = !closed;\n\n let style;\n\n if (flashHeight && !closed) {\n style = { height: `${flashHeight}px` };\n } else if (closed) {\n style = { height: 0, marginTop: 0, zIndex: -1 };\n } else {\n style = {};\n }\n\n const safeContent = DOMPurify.sanitize(content, {\n ALLOWED_TAGS: [\"a\"],\n ALLOWED_ATTR: [\"href\", \"data-method\"],\n ALLOWED_URI_REGEXP: /^\\/[^/]/,\n });\n\n const withIcons: Record<FlashPropsType, IconName | \"\"> = {\n notice: \"icon-gui-ably-badge\",\n success: \"icon-gui-check-outline\",\n error: \"icon-gui-exclamation-triangle-outline\",\n alert: \"icon-gui-exclamation-triangle-outline\",\n info: \"\",\n };\n\n const iconColor: Record<FlashPropsType, ColorClass | \"\"> = {\n notice: \"text-cool-black\",\n success: \"text-cool-black\",\n error: \"text-white\",\n alert: \"text-white\",\n info: \"\",\n };\n\n return (\n <div\n className={`ui-flash-message ui-grid-px ${\n animateEntry ? \"ui-flash-message-enter\" : \"\"\n }`}\n style={style}\n ref={ref}\n data-id=\"ui-flash\"\n data-testid=\"ui-flash\"\n >\n <div\n className={`${FLASH_BG_COLOR[type]} p-8 flex align-center rounded shadow-container-subtle`}\n >\n {withIcons[type] && iconColor[type] && (\n <Icon\n name={withIcons[type]}\n color={iconColor[type]}\n size=\"1.5rem\"\n additionalCSS=\"mr-4 self-baseline\"\n />\n )}\n <p\n className={`ui-text-p1 mr-4 ${FLASH_TEXT_COLOR[type]}`}\n dangerouslySetInnerHTML={{ __html: safeContent }}\n />\n <button\n type=\"button\"\n className=\"p-0 ml-auto self-start focus:outline-none focus-base\"\n onClick={closeFlash}\n >\n {iconColor[type] && (\n <Icon\n name=\"icon-gui-x-mark-outline\"\n color={iconColor[type]}\n size=\"1.5rem\"\n additionalCSS=\"transition-colors\"\n />\n )}\n </button>\n </div>\n </div>\n );\n};\n\n/**\n * Flashes - Container component that renders all active flash messages.\n *\n * Reads from FlashContext and displays each flash message in order.\n * Use this component where you want flash messages to appear in your layout\n * (typically near the top of the page). Filters out removed messages.\n */\nconst Flashes = () => {\n const { flashes } = useFlashContext();\n\n return (\n <div className=\"ui-flash\" data-id={FLASH_DATA_ID}>\n {flashes\n .filter((item) => !item.removed)\n .map((flash) => (\n <Flash key={flash.id} {...flash} />\n ))}\n </div>\n );\n};\n\n/**\n * BackendFlashes - Integration component for server-side flash messages (default export).\n *\n * Receives flash messages from backend as an array of [type, content] tuples and adds\n * them to the FlashContext on mount. Primary use case is hydrating flash messages from\n * Rails or other backend frameworks. Renders the Flashes component to display messages.\n * Must be used within FlashProvider.\n */\nconst BackendFlashes = ({ flashes }: BackendFlashesProps) => {\n const context = useContext(FlashContext);\n const addFlashes = context?.addFlashes;\n\n useEffect(() => {\n if (!addFlashes) {\n console.warn(\"BackendFlashes must be used within FlashProvider\");\n return;\n }\n\n const transformedFlashes = flashes.map((flash) => {\n const [type, content] = flash;\n return { type: type as FlashPropsType, content };\n });\n\n if (transformedFlashes.length > 0) {\n addFlashes(transformedFlashes);\n }\n }, [flashes, addFlashes]);\n\n if (!context) return null;\n\n return <Flashes />;\n};\n\nexport { FLASH_DATA_ID, Flashes, FlashProvider, useFlashContext };\nexport default BackendFlashes;\n"],"names":["React","useEffect","useState","useRef","createContext","useContext","useCallback","useMemo","DOMPurify","Icon","FLASH_DATA_ID","FlashContext","undefined","FlashProvider","children","flashes","setFlashes","removeFlash","flashId","prev","filter","item","id","addFlashes","newFlashes","withIds","flash","some","existing","content","type","map","Math","random","toString","slice","removed","contextValue","Provider","value","useFlashContext","context","Error","FLASH_BG_COLOR","error","success","notice","info","alert","FLASH_TEXT_COLOR","AUTO_HIDE","AUTO_HIDE_TIME","useAutoHide","closeFlash","timeoutId","includes","current","setTimeout","clearTimeout","Flash","ref","closed","setClosed","flashHeight","setFlashHeight","getBoundingClientRect","height","animateEntry","style","marginTop","zIndex","safeContent","sanitize","ALLOWED_TAGS","ALLOWED_ATTR","ALLOWED_URI_REGEXP","withIcons","iconColor","div","className","data-id","data-testid","name","color","size","additionalCSS","p","dangerouslySetInnerHTML","__html","button","onClick","Flashes","key","BackendFlashes","console","warn","transformedFlashes","length"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/core/Flash.tsx"],"sourcesContent":["import React, {\n useEffect,\n useState,\n useRef,\n createContext,\n useContext,\n useCallback,\n useMemo,\n PropsWithChildren,\n} from \"react\";\nimport DOMPurify from \"dompurify\";\nimport Icon from \"./Icon\";\nimport { ColorClass } from \"./styles/colors/types\";\nimport { IconName } from \"./Icon/types\";\n\ntype FlashPropsType = \"error\" | \"success\" | \"notice\" | \"info\" | \"alert\";\n\ntype FlashProps = {\n id: string;\n removed: boolean;\n type: FlashPropsType;\n content: string;\n removeFlash: (id: string) => void;\n};\n\ntype BackendFlashesProps = {\n flashes: string[][];\n};\n\nconst FLASH_DATA_ID = \"ui-flashes\";\n\ntype FlashContextType = {\n flashes: FlashProps[];\n addFlashes: (flashes: Pick<FlashProps, \"type\" | \"content\">[]) => void;\n removeFlash: (id: string) => void;\n};\n\nconst FlashContext = createContext<FlashContextType | undefined>(undefined);\n\ntype FlashProviderProps = PropsWithChildren;\n\n/**\n * FlashProvider - Context provider for managing flash messages throughout the application.\n *\n * Maintains a global list of flash messages and provides methods to add/remove them.\n * Automatically deduplicates messages with the same type and content to prevent duplicates.\n * Use this at the app root level and access via useFlashContext() in child components.\n */\nconst FlashProvider = ({ children }: FlashProviderProps) => {\n const [flashes, setFlashes] = useState<FlashProps[]>([]);\n\n const removeFlash = useCallback((flashId: string) => {\n setFlashes((prev) => prev.filter((item) => item.id !== flashId));\n }, []);\n\n const addFlashes = useCallback(\n (newFlashes: Pick<FlashProps, \"type\" | \"content\">[]) => {\n setFlashes((prev) => {\n const withIds = newFlashes\n .filter(\n (flash) =>\n !prev.some(\n (existing) =>\n existing.content === flash.content &&\n existing.type === flash.type,\n ),\n )\n .map((flash) => ({\n ...flash,\n id: Math.random().toString(36).slice(2),\n removed: false,\n removeFlash,\n }));\n\n return [...prev, ...withIds];\n });\n },\n [removeFlash],\n );\n\n const contextValue = useMemo(\n () => ({ flashes, addFlashes, removeFlash }),\n [flashes, addFlashes, removeFlash],\n );\n\n return (\n <FlashContext.Provider value={contextValue}>\n {children}\n </FlashContext.Provider>\n );\n};\n\nconst useFlashContext = () => {\n const context = useContext(FlashContext);\n if (context === undefined) {\n throw new Error(\"useFlashContext must be used within FlashProvider\");\n }\n return context;\n};\n\nconst FLASH_BG_COLOR = {\n error: \"bg-gui-error\",\n success: \"bg-zingy-green\",\n notice: \"bg-electric-cyan\",\n info: \"bg-electric-cyan\",\n alert: \"bg-active-orange\",\n};\n\nconst FLASH_TEXT_COLOR = {\n error: \"text-white\",\n success: \"text-cool-black\",\n notice: \"text-cool-black\",\n info: \"text-cool-black\",\n alert: \"text-white\",\n};\n\nconst AUTO_HIDE = [\"success\", \"info\", \"notice\"];\nconst AUTO_HIDE_TIME = 8000;\n\nconst useAutoHide = (type: string, closeFlash: () => void) => {\n const timeoutId = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n if (AUTO_HIDE.includes(type)) {\n timeoutId.current = setTimeout(() => {\n closeFlash();\n }, AUTO_HIDE_TIME);\n }\n\n return () => {\n if (timeoutId.current) {\n clearTimeout(timeoutId.current);\n }\n };\n }, [type, closeFlash]);\n};\n\n/**\n * Flash - Individual flash message component with animations and auto-dismiss.\n *\n * Displays a colored notification banner with an icon, message content, and close button.\n * Success/info/notice messages auto-hide after 8 seconds. Error/alert messages require\n * manual dismissal. Uses CSS animations for smooth entry/exit and height transitions\n * on close. Content is sanitized with DOMPurify to allow safe HTML links from backend.\n */\nconst Flash = ({ id, type, content, removeFlash }: FlashProps) => {\n const ref = useRef<HTMLDivElement>(null);\n const [closed, setClosed] = useState(false);\n const [flashHeight, setFlashHeight] = useState(0);\n\n const closeFlash = () => {\n if (ref.current) {\n setFlashHeight(ref.current.getBoundingClientRect().height);\n }\n\n setClosed(true);\n\n setTimeout(() => {\n if (id) {\n removeFlash(id);\n }\n }, 100);\n };\n\n useAutoHide(type, closeFlash);\n\n const animateEntry = !closed;\n\n let style;\n\n if (flashHeight && !closed) {\n style = { height: `${flashHeight}px` };\n } else if (closed) {\n style = { height: 0, marginTop: 0, zIndex: -1 };\n } else {\n style = {};\n }\n\n const safeContent = DOMPurify.sanitize(content, {\n ALLOWED_TAGS: [\"a\"],\n ALLOWED_ATTR: [\"href\", \"data-method\"],\n ALLOWED_URI_REGEXP: /^\\/[^/]/,\n });\n\n const withIcons: Record<FlashPropsType, IconName | \"\"> = {\n notice: \"icon-gui-ably-badge\",\n success: \"icon-gui-check-outline\",\n error: \"icon-gui-exclamation-triangle-outline\",\n alert: \"icon-gui-exclamation-triangle-outline\",\n info: \"\",\n };\n\n const iconColor: Record<FlashPropsType, ColorClass | \"\"> = {\n notice: \"text-cool-black\",\n success: \"text-cool-black\",\n error: \"text-white\",\n alert: \"text-white\",\n info: \"\",\n };\n\n return (\n <div\n className={`ui-flash-message ui-grid-px ${\n animateEntry ? \"ui-flash-message-enter\" : \"\"\n }`}\n style={style}\n ref={ref}\n data-id=\"ui-flash\"\n data-testid=\"ui-flash\"\n >\n <div\n className={`${FLASH_BG_COLOR[type]} p-8 flex align-center rounded shadow-container-subtle`}\n >\n {withIcons[type] && iconColor[type] && (\n <Icon\n name={withIcons[type]}\n color={iconColor[type]}\n size=\"1.5rem\"\n additionalCSS=\"mr-4 self-baseline\"\n />\n )}\n <p\n className={`ui-text-p1 mr-4 ${FLASH_TEXT_COLOR[type]}`}\n // Sanitised inline-markup allowlist (a + relative href) applied above\n // via DOMPurify. Slice 3a replaces the local config with the shared\n // sanitizeInlineMarkup helper.\n // eslint-disable-next-line react/no-danger\n dangerouslySetInnerHTML={{ __html: safeContent }}\n />\n <button\n type=\"button\"\n className=\"p-0 ml-auto self-start focus:outline-none focus-base\"\n onClick={closeFlash}\n >\n {iconColor[type] && (\n <Icon\n name=\"icon-gui-x-mark-outline\"\n color={iconColor[type]}\n size=\"1.5rem\"\n additionalCSS=\"transition-colors\"\n />\n )}\n </button>\n </div>\n </div>\n );\n};\n\n/**\n * Flashes - Container component that renders all active flash messages.\n *\n * Reads from FlashContext and displays each flash message in order.\n * Use this component where you want flash messages to appear in your layout\n * (typically near the top of the page). Filters out removed messages.\n */\nconst Flashes = () => {\n const { flashes } = useFlashContext();\n\n return (\n <div className=\"ui-flash\" data-id={FLASH_DATA_ID}>\n {flashes\n .filter((item) => !item.removed)\n .map((flash) => (\n <Flash key={flash.id} {...flash} />\n ))}\n </div>\n );\n};\n\n/**\n * BackendFlashes - Integration component for server-side flash messages (default export).\n *\n * Receives flash messages from backend as an array of [type, content] tuples and adds\n * them to the FlashContext on mount. Primary use case is hydrating flash messages from\n * Rails or other backend frameworks. Renders the Flashes component to display messages.\n * Must be used within FlashProvider.\n */\nconst BackendFlashes = ({ flashes }: BackendFlashesProps) => {\n const context = useContext(FlashContext);\n const addFlashes = context?.addFlashes;\n\n useEffect(() => {\n if (!addFlashes) {\n console.warn(\"BackendFlashes must be used within FlashProvider\");\n return;\n }\n\n const transformedFlashes = flashes.map((flash) => {\n const [type, content] = flash;\n return { type: type as FlashPropsType, content };\n });\n\n if (transformedFlashes.length > 0) {\n addFlashes(transformedFlashes);\n }\n }, [flashes, addFlashes]);\n\n if (!context) return null;\n\n return <Flashes />;\n};\n\nexport { FLASH_DATA_ID, Flashes, FlashProvider, useFlashContext };\nexport default BackendFlashes;\n"],"names":["React","useEffect","useState","useRef","createContext","useContext","useCallback","useMemo","DOMPurify","Icon","FLASH_DATA_ID","FlashContext","undefined","FlashProvider","children","flashes","setFlashes","removeFlash","flashId","prev","filter","item","id","addFlashes","newFlashes","withIds","flash","some","existing","content","type","map","Math","random","toString","slice","removed","contextValue","Provider","value","useFlashContext","context","Error","FLASH_BG_COLOR","error","success","notice","info","alert","FLASH_TEXT_COLOR","AUTO_HIDE","AUTO_HIDE_TIME","useAutoHide","closeFlash","timeoutId","includes","current","setTimeout","clearTimeout","Flash","ref","closed","setClosed","flashHeight","setFlashHeight","getBoundingClientRect","height","animateEntry","style","marginTop","zIndex","safeContent","sanitize","ALLOWED_TAGS","ALLOWED_ATTR","ALLOWED_URI_REGEXP","withIcons","iconColor","div","className","data-id","data-testid","name","color","size","additionalCSS","p","dangerouslySetInnerHTML","__html","button","onClick","Flashes","key","BackendFlashes","console","warn","transformedFlashes","length"],"mappings":"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"}
|
package/core/Loader.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/Loader.tsx"],"sourcesContent":["import React from \"react\";\n\ntype LoaderProps = {\n size?: string;\n ringColor?: string;\n additionalCSS?: string;\n};\n\nconst Loader = ({\n ringColor = \"text-dark-grey\",\n size = \"1.5rem\",\n additionalCSS = \"\",\n}: LoaderProps) => (\n <svg\n className={`${ringColor} ${additionalCSS}`}\n style={{ width: size, height: size }}\n height=\"24\"\n viewBox=\"0 0 24 24\"\n width=\"24\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <style\n dangerouslySetInnerHTML={{\n __html: `\n @keyframes chunk-animation {\n to {\n transform: rotate(360deg);\n }\n }\n\n .chunk {\n transform: rotate(0deg);\n transform-origin: center;\n animation: chunk-animation 0.6s cubic-bezier(.44,.15,.66,.98) forwards infinite;\n }\n `,\n }}\n ></style>\n <path\n fill=\"currentColor\"\n d=\"m12 1.99976c-1.9778 0-3.91121.58649-5.5557 1.6853s-2.92621 2.6606-3.68309 4.48786c-.75688 1.82728-.95491 3.83788-.56906 5.77778.38585 1.9398 1.33826 3.7216 2.73679 5.1201 1.39852 1.3985 3.18035 2.351 5.12016 2.7368 1.9398.3859 3.9505.1878 5.7777-.569 1.8273-.7569 3.3891-2.0387 4.4879-3.6831 1.0988-1.6445 1.6853-3.5779 1.6853-5.5557 0-1.3133-.2587-2.61362-.7612-3.82688-.5025-1.21325-1.2391-2.31565-2.1677-3.24423-.9286-.92859-2.031-1.66518-3.2443-2.16773-1.2132-.50255-2.5136-.7612-3.8268-.7612zm0 18.00004c-1.5822 0-3.12896-.4692-4.44456-1.3483-1.31559-.879-2.34097-2.1285-2.94647-3.5903s-.76393-3.0703-.45525-4.6222c.30868-1.55181 1.07061-2.97728 2.18943-4.0961s2.54428-1.88074 4.09615-2.18943c1.5518-.30868 3.1604-.15025 4.6222.45525s2.7112 1.63088 3.5903 2.94647c.879 1.3156 1.3482 2.86231 1.3482 4.44461 0 2.1217-.8428 4.1565-2.3431 5.6568s-3.5352 2.3432-5.6569 2.3432z\"\n opacity=\".5\"\n />\n <path\n className=\"chunk\"\n d=\"m20 11.9998h2c0-1.3133-.2587-2.61362-.7612-3.82688-.5026-1.21325-1.2391-2.31565-2.1677-3.24423-.9286-.92859-2.031-1.66518-3.2443-2.16773-1.2132-.50255-2.5136-.7612-3.8268-.7612v2c2.1217 0 4.1566.84285 5.6569 2.34314 1.5002 1.50029 2.3431 3.53512 2.3431 5.6569z\"\n fill=\"#ff5416\"\n />\n </svg>\n);\n\nexport default Loader;\n"],"names":["React","Loader","ringColor","size","additionalCSS","svg","className","style","width","height","viewBox","xmlns","dangerouslySetInnerHTML","__html","path","fill","d","opacity"],"mappings":"AAAA,OAAOA,UAAW,OAAQ,CAQ1B,MAAMC,OAAS,CAAC,CACdC,UAAY,gBAAgB,CAC5BC,KAAO,QAAQ,CACfC,cAAgB,EAAE,CACN,GACZ,oBAACC,OACCC,UAAW,CAAC,EAAEJ,UAAU,CAAC,EAAEE,cAAc,CAAC,CAC1CG,MAAO,CAAEC,MAAOL,KAAMM,OAAQN,IAAK,EACnCM,OAAO,KACPC,QAAQ,YACRF,MAAM,KACNG,MAAM,8BAEN,oBAACJ,
|
|
1
|
+
{"version":3,"sources":["../../src/core/Loader.tsx"],"sourcesContent":["import React from \"react\";\n\ntype LoaderProps = {\n size?: string;\n ringColor?: string;\n additionalCSS?: string;\n};\n\nconst Loader = ({\n ringColor = \"text-dark-grey\",\n size = \"1.5rem\",\n additionalCSS = \"\",\n}: LoaderProps) => (\n <svg\n className={`${ringColor} ${additionalCSS}`}\n style={{ width: size, height: size }}\n height=\"24\"\n viewBox=\"0 0 24 24\"\n width=\"24\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <style\n // Hardcoded CSS keyframes — no caller-supplied input. Required because\n // styled-components / CSS-in-JS would force a build step on consumers.\n // eslint-disable-next-line react/no-danger\n dangerouslySetInnerHTML={{\n __html: `\n @keyframes chunk-animation {\n to {\n transform: rotate(360deg);\n }\n }\n\n .chunk {\n transform: rotate(0deg);\n transform-origin: center;\n animation: chunk-animation 0.6s cubic-bezier(.44,.15,.66,.98) forwards infinite;\n }\n `,\n }}\n ></style>\n <path\n fill=\"currentColor\"\n d=\"m12 1.99976c-1.9778 0-3.91121.58649-5.5557 1.6853s-2.92621 2.6606-3.68309 4.48786c-.75688 1.82728-.95491 3.83788-.56906 5.77778.38585 1.9398 1.33826 3.7216 2.73679 5.1201 1.39852 1.3985 3.18035 2.351 5.12016 2.7368 1.9398.3859 3.9505.1878 5.7777-.569 1.8273-.7569 3.3891-2.0387 4.4879-3.6831 1.0988-1.6445 1.6853-3.5779 1.6853-5.5557 0-1.3133-.2587-2.61362-.7612-3.82688-.5025-1.21325-1.2391-2.31565-2.1677-3.24423-.9286-.92859-2.031-1.66518-3.2443-2.16773-1.2132-.50255-2.5136-.7612-3.8268-.7612zm0 18.00004c-1.5822 0-3.12896-.4692-4.44456-1.3483-1.31559-.879-2.34097-2.1285-2.94647-3.5903s-.76393-3.0703-.45525-4.6222c.30868-1.55181 1.07061-2.97728 2.18943-4.0961s2.54428-1.88074 4.09615-2.18943c1.5518-.30868 3.1604-.15025 4.6222.45525s2.7112 1.63088 3.5903 2.94647c.879 1.3156 1.3482 2.86231 1.3482 4.44461 0 2.1217-.8428 4.1565-2.3431 5.6568s-3.5352 2.3432-5.6569 2.3432z\"\n opacity=\".5\"\n />\n <path\n className=\"chunk\"\n d=\"m20 11.9998h2c0-1.3133-.2587-2.61362-.7612-3.82688-.5026-1.21325-1.2391-2.31565-2.1677-3.24423-.9286-.92859-2.031-1.66518-3.2443-2.16773-1.2132-.50255-2.5136-.7612-3.8268-.7612v2c2.1217 0 4.1566.84285 5.6569 2.34314 1.5002 1.50029 2.3431 3.53512 2.3431 5.6569z\"\n fill=\"#ff5416\"\n />\n </svg>\n);\n\nexport default Loader;\n"],"names":["React","Loader","ringColor","size","additionalCSS","svg","className","style","width","height","viewBox","xmlns","dangerouslySetInnerHTML","__html","path","fill","d","opacity"],"mappings":"AAAA,OAAOA,UAAW,OAAQ,CAQ1B,MAAMC,OAAS,CAAC,CACdC,UAAY,gBAAgB,CAC5BC,KAAO,QAAQ,CACfC,cAAgB,EAAE,CACN,GACZ,oBAACC,OACCC,UAAW,CAAC,EAAEJ,UAAU,CAAC,EAAEE,cAAc,CAAC,CAC1CG,MAAO,CAAEC,MAAOL,KAAMM,OAAQN,IAAK,EACnCM,OAAO,KACPC,QAAQ,YACRF,MAAM,KACNG,MAAM,8BAEN,oBAACJ,SAICK,wBAAyB,CACvBC,OAAQ;AAChB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,CAAC,AACC,IAEF,oBAACC,QACCC,KAAK,eACLC,EAAE,+2BACFC,QAAQ,OAEV,oBAACH,QACCR,UAAU,QACVU,EAAE,uQACFD,KAAK,YAKX,gBAAed,MAAO"}
|
package/core/Notice.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/Notice.tsx"],"sourcesContent":["import React, { useEffect, useRef, useState } from \"react\";\nimport DOMPurify from \"dompurify\";\n\nimport { ColorClass, ColorThemeSet } from \"./styles/colors/types\";\nimport Icon from \"./Icon\";\nimport cn from \"./utils/cn.js\";\nimport NoticeScripts from \"./Notice/component.js\";\nimport useRailsUjsLinks from \"./hooks/use-rails-ujs-hooks\";\n\n// TODO(jamiehenson):\n// This type is a bit messed up currently due to the NoticeScripts import being interpreted as NoticeProps.\n// Plan is to TS-ify the JS assets too, so this can be rectified then. The NoticeScripts-oriented props are\n// the ones after the line break.\nexport type NoticeProps = {\n buttonLink?: string;\n buttonLabel?: string;\n bodyText?: string;\n title?: string;\n closeBtn?: boolean;\n config?: {\n options: {\n collapse: boolean;\n };\n noticeId: string | number;\n cookieId: string;\n };\n bgColor?: string;\n textColor?: ColorClass | ColorThemeSet;\n onClose?: () => void;\n\n bannerContainer?: Element | null;\n cookieId?: string;\n noticeId?: string;\n options?: { collapse: boolean };\n};\n\nconst defaultTextColor = \"text-neutral-1300 dark:text-neutral-000\";\n\nconst contentWrapperClasses = \"w-full pr-2 ui-text-p4 self-center\";\n\nconst Notice = ({\n buttonLink,\n buttonLabel,\n bodyText,\n title,\n config,\n closeBtn,\n bgColor = \"bg-orange-100 dark:bg-orange-1100\",\n textColor = defaultTextColor,\n onClose,\n}: NoticeProps) => {\n const contentRef = useRef<HTMLSpanElement>(null);\n const [isClosing, setIsClosing] = useState(false);\n useRailsUjsLinks(contentRef);\n\n useEffect(() => {\n const cleanup = NoticeScripts({\n bannerContainer: document.querySelector('[data-id=\"ui-notice\"]'),\n cookieId: config?.cookieId,\n noticeId: config?.noticeId,\n options: {\n collapse: config?.options?.collapse || false,\n },\n });\n\n return cleanup;\n }, [config?.cookieId, config?.noticeId, config?.options?.collapse]);\n\n const safeContent = DOMPurify.sanitize(bodyText ?? \"\", {\n ALLOWED_TAGS: [\"a\"],\n ALLOWED_ATTR: [\"href\", \"data-method\"],\n ALLOWED_URI_REGEXP: /^\\/[^/]/,\n });\n\n const isSafeButtonLink =\n typeof buttonLink === \"string\" &&\n (/^\\/(?!\\/)/.test(buttonLink) || /^https?:\\/\\//.test(buttonLink));\n\n // have to add the style classes here as src/core/Notice/component.css is not being properly imported or distributed when ably-ui is used as a package.\n return (\n <div\n className={cn(\n \"ui-announcement relative z-[60]\",\n isClosing\n ? \"ui-announcement-hidden max-h-0 -translate-y-full opacity-0 overflow-hidden\"\n : \"ui-announcement-visible\",\n bgColor,\n textColor,\n )}\n data-id=\"ui-notice\"\n >\n <div className=\"ui-grid-px py-4 max-w-screen-xl mx-auto flex items-start\">\n <div className={cn(contentWrapperClasses, textColor)}>\n <strong className=\"font-bold whitespace-nowrap pr-1\">{title}</strong>\n <span\n ref={contentRef}\n className=\"pr-1\"\n dangerouslySetInnerHTML={{\n __html: safeContent,\n }}\n ></span>\n {buttonLabel &&\n (isSafeButtonLink ? (\n <a\n href={buttonLink}\n className=\"focus-base transition-colors cursor-pointer whitespace-nowrap text-gui-blue-default-light dark:text-gui-blue-default-dark\"\n >\n {buttonLabel}\n </a>\n ) : (\n <span className=\"focus-base transition-colors cursor-pointer whitespace-nowrap text-gui-blue-default-light dark:text-gui-blue-default-dark\">\n {buttonLabel}\n </span>\n ))}\n </div>\n\n {closeBtn && (\n <button\n type=\"button\"\n className=\"ml-auto h-5 w-5 border-none bg-none self-baseline outline-none focus:outline-none focus:ring-0 focus:border-transparent\"\n onClick={() => {\n setIsClosing(true);\n setTimeout(() => {\n document.dispatchEvent(new CustomEvent(\"notice-closed\"));\n onClose?.();\n }, 300);\n }}\n >\n <Icon\n name=\"icon-gui-x-mark-outline\"\n size=\"1.25rem\"\n color={textColor}\n />\n </button>\n )}\n </div>\n </div>\n );\n};\n\nexport default Notice;\n"],"names":["React","useEffect","useRef","useState","DOMPurify","Icon","cn","NoticeScripts","useRailsUjsLinks","defaultTextColor","contentWrapperClasses","Notice","buttonLink","buttonLabel","bodyText","title","config","closeBtn","bgColor","textColor","onClose","contentRef","isClosing","setIsClosing","cleanup","bannerContainer","document","querySelector","cookieId","noticeId","options","collapse","safeContent","sanitize","ALLOWED_TAGS","ALLOWED_ATTR","ALLOWED_URI_REGEXP","isSafeButtonLink","test","div","className","data-id","strong","span","ref","dangerouslySetInnerHTML","__html","a","href","button","type","onClick","setTimeout","dispatchEvent","CustomEvent","name","size","color"],"mappings":"AAAA,OAAOA,OAASC,SAAS,CAAEC,MAAM,CAAEC,QAAQ,KAAQ,OAAQ,AAC3D,QAAOC,cAAe,WAAY,AAGlC,QAAOC,SAAU,QAAS,AAC1B,QAAOC,OAAQ,eAAgB,AAC/B,QAAOC,kBAAmB,uBAAwB,AAClD,QAAOC,qBAAsB,6BAA8B,CA6B3D,MAAMC,iBAAmB,0CAEzB,MAAMC,sBAAwB,qCAE9B,MAAMC,OAAS,CAAC,CACdC,UAAU,CACVC,WAAW,CACXC,QAAQ,CACRC,KAAK,CACLC,MAAM,CACNC,QAAQ,CACRC,QAAU,mCAAmC,CAC7CC,UAAYV,gBAAgB,CAC5BW,OAAO,CACK,IACZ,MAAMC,WAAanB,OAAwB,MAC3C,KAAM,CAACoB,UAAWC,aAAa,CAAGpB,SAAS,OAC3CK,iBAAiBa,YAEjBpB,UAAU,KACR,MAAMuB,QAAUjB,cAAc,CAC5BkB,gBAAiBC,SAASC,aAAa,CAAC,yBACxCC,SAAUZ,QAAQY,SAClBC,SAAUb,QAAQa,SAClBC,QAAS,CACPC,SAAUf,QAAQc,SAASC,UAAY,KACzC,CACF,GAEA,OAAOP,OACT,EAAG,CAACR,QAAQY,SAAUZ,QAAQa,SAAUb,QAAQc,SAASC,SAAS,EAElE,MAAMC,YAAc5B,UAAU6B,QAAQ,CAACnB,UAAY,GAAI,CACrDoB,aAAc,CAAC,IAAI,CACnBC,aAAc,CAAC,OAAQ,cAAc,CACrCC,mBAAoB,SACtB,GAEA,MAAMC,iBACJ,OAAOzB,aAAe,UACrB,CAAA,YAAY0B,IAAI,CAAC1B,aAAe,eAAe0B,IAAI,CAAC1B,WAAU,EAGjE,OACE,oBAAC2B,OACCC,UAAWlC,GACT,kCACAgB,UACI,6EACA,0BACJJ,QACAC,WAEFsB,UAAQ,aAER,oBAACF,OAAIC,UAAU,4DACb,oBAACD,OAAIC,UAAWlC,GAAGI,sBAAuBS,YACxC,oBAACuB,UAAOF,UAAU,oCAAoCzB,OACtD,oBAAC4B,QACCC,IAAKvB,WACLmB,UAAU,
|
|
1
|
+
{"version":3,"sources":["../../src/core/Notice.tsx"],"sourcesContent":["import React, { useEffect, useRef, useState } from \"react\";\nimport DOMPurify from \"dompurify\";\n\nimport { ColorClass, ColorThemeSet } from \"./styles/colors/types\";\nimport Icon from \"./Icon\";\nimport cn from \"./utils/cn.js\";\nimport NoticeScripts from \"./Notice/component.js\";\nimport useRailsUjsLinks from \"./hooks/use-rails-ujs-hooks\";\n\n// TODO(jamiehenson):\n// This type is a bit messed up currently due to the NoticeScripts import being interpreted as NoticeProps.\n// Plan is to TS-ify the JS assets too, so this can be rectified then. The NoticeScripts-oriented props are\n// the ones after the line break.\nexport type NoticeProps = {\n buttonLink?: string;\n buttonLabel?: string;\n bodyText?: string;\n title?: string;\n closeBtn?: boolean;\n config?: {\n options: {\n collapse: boolean;\n };\n noticeId: string | number;\n cookieId: string;\n };\n bgColor?: string;\n textColor?: ColorClass | ColorThemeSet;\n onClose?: () => void;\n\n bannerContainer?: Element | null;\n cookieId?: string;\n noticeId?: string;\n options?: { collapse: boolean };\n};\n\nconst defaultTextColor = \"text-neutral-1300 dark:text-neutral-000\";\n\nconst contentWrapperClasses = \"w-full pr-2 ui-text-p4 self-center\";\n\nconst Notice = ({\n buttonLink,\n buttonLabel,\n bodyText,\n title,\n config,\n closeBtn,\n bgColor = \"bg-orange-100 dark:bg-orange-1100\",\n textColor = defaultTextColor,\n onClose,\n}: NoticeProps) => {\n const contentRef = useRef<HTMLSpanElement>(null);\n const [isClosing, setIsClosing] = useState(false);\n useRailsUjsLinks(contentRef);\n\n useEffect(() => {\n const cleanup = NoticeScripts({\n bannerContainer: document.querySelector('[data-id=\"ui-notice\"]'),\n cookieId: config?.cookieId,\n noticeId: config?.noticeId,\n options: {\n collapse: config?.options?.collapse || false,\n },\n });\n\n return cleanup;\n }, [config?.cookieId, config?.noticeId, config?.options?.collapse]);\n\n const safeContent = DOMPurify.sanitize(bodyText ?? \"\", {\n ALLOWED_TAGS: [\"a\"],\n ALLOWED_ATTR: [\"href\", \"data-method\"],\n ALLOWED_URI_REGEXP: /^\\/[^/]/,\n });\n\n const isSafeButtonLink =\n typeof buttonLink === \"string\" &&\n (/^\\/(?!\\/)/.test(buttonLink) || /^https?:\\/\\//.test(buttonLink));\n\n // have to add the style classes here as src/core/Notice/component.css is not being properly imported or distributed when ably-ui is used as a package.\n return (\n <div\n className={cn(\n \"ui-announcement relative z-[60]\",\n isClosing\n ? \"ui-announcement-hidden max-h-0 -translate-y-full opacity-0 overflow-hidden\"\n : \"ui-announcement-visible\",\n bgColor,\n textColor,\n )}\n data-id=\"ui-notice\"\n >\n <div className=\"ui-grid-px py-4 max-w-screen-xl mx-auto flex items-start\">\n <div className={cn(contentWrapperClasses, textColor)}>\n <strong className=\"font-bold whitespace-nowrap pr-1\">{title}</strong>\n <span\n ref={contentRef}\n className=\"pr-1\"\n // Sanitised inline-markup allowlist (a + relative href) applied above\n // via DOMPurify. Slice 3a replaces the local config with the shared\n // sanitizeInlineMarkup helper.\n // eslint-disable-next-line react/no-danger\n dangerouslySetInnerHTML={{\n __html: safeContent,\n }}\n ></span>\n {buttonLabel &&\n (isSafeButtonLink ? (\n <a\n href={buttonLink}\n className=\"focus-base transition-colors cursor-pointer whitespace-nowrap text-gui-blue-default-light dark:text-gui-blue-default-dark\"\n >\n {buttonLabel}\n </a>\n ) : (\n <span className=\"focus-base transition-colors cursor-pointer whitespace-nowrap text-gui-blue-default-light dark:text-gui-blue-default-dark\">\n {buttonLabel}\n </span>\n ))}\n </div>\n\n {closeBtn && (\n <button\n type=\"button\"\n className=\"ml-auto h-5 w-5 border-none bg-none self-baseline outline-none focus:outline-none focus:ring-0 focus:border-transparent\"\n onClick={() => {\n setIsClosing(true);\n setTimeout(() => {\n document.dispatchEvent(new CustomEvent(\"notice-closed\"));\n onClose?.();\n }, 300);\n }}\n >\n <Icon\n name=\"icon-gui-x-mark-outline\"\n size=\"1.25rem\"\n color={textColor}\n />\n </button>\n )}\n </div>\n </div>\n );\n};\n\nexport default Notice;\n"],"names":["React","useEffect","useRef","useState","DOMPurify","Icon","cn","NoticeScripts","useRailsUjsLinks","defaultTextColor","contentWrapperClasses","Notice","buttonLink","buttonLabel","bodyText","title","config","closeBtn","bgColor","textColor","onClose","contentRef","isClosing","setIsClosing","cleanup","bannerContainer","document","querySelector","cookieId","noticeId","options","collapse","safeContent","sanitize","ALLOWED_TAGS","ALLOWED_ATTR","ALLOWED_URI_REGEXP","isSafeButtonLink","test","div","className","data-id","strong","span","ref","dangerouslySetInnerHTML","__html","a","href","button","type","onClick","setTimeout","dispatchEvent","CustomEvent","name","size","color"],"mappings":"AAAA,OAAOA,OAASC,SAAS,CAAEC,MAAM,CAAEC,QAAQ,KAAQ,OAAQ,AAC3D,QAAOC,cAAe,WAAY,AAGlC,QAAOC,SAAU,QAAS,AAC1B,QAAOC,OAAQ,eAAgB,AAC/B,QAAOC,kBAAmB,uBAAwB,AAClD,QAAOC,qBAAsB,6BAA8B,CA6B3D,MAAMC,iBAAmB,0CAEzB,MAAMC,sBAAwB,qCAE9B,MAAMC,OAAS,CAAC,CACdC,UAAU,CACVC,WAAW,CACXC,QAAQ,CACRC,KAAK,CACLC,MAAM,CACNC,QAAQ,CACRC,QAAU,mCAAmC,CAC7CC,UAAYV,gBAAgB,CAC5BW,OAAO,CACK,IACZ,MAAMC,WAAanB,OAAwB,MAC3C,KAAM,CAACoB,UAAWC,aAAa,CAAGpB,SAAS,OAC3CK,iBAAiBa,YAEjBpB,UAAU,KACR,MAAMuB,QAAUjB,cAAc,CAC5BkB,gBAAiBC,SAASC,aAAa,CAAC,yBACxCC,SAAUZ,QAAQY,SAClBC,SAAUb,QAAQa,SAClBC,QAAS,CACPC,SAAUf,QAAQc,SAASC,UAAY,KACzC,CACF,GAEA,OAAOP,OACT,EAAG,CAACR,QAAQY,SAAUZ,QAAQa,SAAUb,QAAQc,SAASC,SAAS,EAElE,MAAMC,YAAc5B,UAAU6B,QAAQ,CAACnB,UAAY,GAAI,CACrDoB,aAAc,CAAC,IAAI,CACnBC,aAAc,CAAC,OAAQ,cAAc,CACrCC,mBAAoB,SACtB,GAEA,MAAMC,iBACJ,OAAOzB,aAAe,UACrB,CAAA,YAAY0B,IAAI,CAAC1B,aAAe,eAAe0B,IAAI,CAAC1B,WAAU,EAGjE,OACE,oBAAC2B,OACCC,UAAWlC,GACT,kCACAgB,UACI,6EACA,0BACJJ,QACAC,WAEFsB,UAAQ,aAER,oBAACF,OAAIC,UAAU,4DACb,oBAACD,OAAIC,UAAWlC,GAAGI,sBAAuBS,YACxC,oBAACuB,UAAOF,UAAU,oCAAoCzB,OACtD,oBAAC4B,QACCC,IAAKvB,WACLmB,UAAU,OAKVK,wBAAyB,CACvBC,OAAQd,WACV,IAEDnB,aACEwB,CAAAA,iBACC,oBAACU,KACCC,KAAMpC,WACN4B,UAAU,6HAET3B,aAGH,oBAAC8B,QAAKH,UAAU,6HACb3B,YAEL,GAGHI,UACC,oBAACgC,UACCC,KAAK,SACLV,UAAU,0HACVW,QAAS,KACP5B,aAAa,MACb6B,WAAW,KACT1B,SAAS2B,aAAa,CAAC,IAAIC,YAAY,kBACvClC,WACF,EAAG,IACL,GAEA,oBAACf,MACCkD,KAAK,0BACLC,KAAK,UACLC,MAAOtC,cAOrB,CAEA,gBAAeR,MAAO"}
|
package/core/SessionData.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/SessionData.tsx"],"sourcesContent":["import React, {\n createContext,\n useContext,\n PropsWithChildren,\n useMemo,\n} from \"react\";\nimport useSWR from \"swr\";\n\n// Feature flag for enabling credentials in fetch requests\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\ndeclare const __ENABLE_FETCH_WITH_CREDENTIALS__: boolean | undefined;\n\ntype SessionUser = {\n firstName?: string;\n lastName?: string;\n email?: string;\n};\n\ntype SessionData = {\n user?: SessionUser;\n} | null;\n\ntype SessionDataContextType = {\n sessionData: SessionData;\n isLoading: boolean;\n error: Error | undefined;\n};\n\nconst SessionDataContext = createContext<SessionDataContextType | undefined>(\n undefined,\n);\n\ntype SessionDataProviderProps = PropsWithChildren<{\n sessionDataUrl?: string;\n}>;\n\n/**\n * fetcher - Retrieves session data from a backend API endpoint.\n *\n * Makes a JSON request to fetch current user session information. Optionally includes\n * credentials (cookies) based on the __ENABLE_FETCH_WITH_CREDENTIALS__ feature flag.\n * Handles \"not-found\" responses gracefully by returning null instead of throwing.\n * Validates response is JSON to prevent parsing errors.\n */\nconst fetcher = async (url: string): Promise<SessionData> => {\n const options: RequestInit = {\n headers: {\n accept: \"application/json\",\n },\n cache: \"no-cache\",\n };\n\n // Check if credentials should be included (feature flag)\n if (\n typeof __ENABLE_FETCH_WITH_CREDENTIALS__ !== \"undefined\" &&\n __ENABLE_FETCH_WITH_CREDENTIALS__\n ) {\n options.credentials = \"include\";\n }\n\n const res = await fetch(url, options);\n\n if (!res.ok) {\n throw new Error(\"Failed to fetch session data\");\n }\n\n const contentType = res.headers.get(\"content-type\");\n if (!contentType?.includes(\"application/json\")) {\n throw new Error(\"Session endpoint is not serving json\");\n }\n\n const data = await res.json();\n\n // Handle \"not found\" error gracefully\n if (data.error === \"not-found\") {\n return null;\n }\n\n return data;\n};\n\nconst SESSION_SWR_OPTIONS = {\n revalidateOnFocus: false,\n revalidateOnReconnect: false,\n shouldRetryOnError: false,\n onError: (err: Error) => {\n console.warn(\"Could not fetch session data due to error:\", err);\n },\n};\n\n/**\n * SessionDataProvider - Context provider for managing user session data throughout the app.\n *\n * Fetches and caches session data using SWR (stale-while-revalidate) from the provided URL.\n * Exposes session data, loading state, and errors through context. Use this at the app root\n * level and access via useSessionData() hook in child components. Disables auto-revalidation\n * to avoid excessive network requests - session data is fetched once on mount.\n */\nexport const SessionDataProvider = ({\n children,\n sessionDataUrl,\n}: SessionDataProviderProps) => {\n const { data, error, isLoading } = useSWR<SessionData>(\n sessionDataUrl || null,\n fetcher,\n SESSION_SWR_OPTIONS,\n );\n\n const contextValue = useMemo(\n () => ({\n sessionData: data ?? null,\n isLoading,\n error,\n }),\n [data, isLoading, error],\n );\n\n return (\n <SessionDataContext.Provider value={contextValue}>\n {children}\n </SessionDataContext.Provider>\n );\n};\n\n/**\n * useSessionData - Hook to access session data from SessionDataProvider context.\n *\n * Returns the current user session data, loading state, and any errors from the provider.\n * Must be used within a SessionDataProvider or will throw an error. Use this hook in\n * components that need to read or display user session information.\n */\nexport const useSessionData = () => {\n const context = useContext(SessionDataContext);\n if (context === undefined) {\n throw new Error(\"useSessionData must be used within SessionDataProvider\");\n }\n return context;\n};\n\n/**\n * useSessionDataDirect - Direct session data hook without requiring a provider (legacy).\n *\n * Fetches session data using SWR directly, bypassing the context provider pattern.\n * Provided for backward compatibility with code that doesn't use SessionDataProvider.\n * For new code, prefer using SessionDataProvider + useSessionData() for better\n * performance and centralized session management.\n */\nexport const useSessionDataDirect = (sessionDataUrl?: string) => {\n const { data, error, isLoading } = useSWR<SessionData>(\n sessionDataUrl || null,\n fetcher,\n SESSION_SWR_OPTIONS,\n );\n\n return {\n sessionData: data ?? null,\n isLoading,\n error,\n };\n};\n"],"names":["React","createContext","useContext","useMemo","useSWR","SessionDataContext","undefined","fetcher","url","options","headers","accept","cache","credentials","res","fetch","ok","Error","contentType","get","includes","data","json","error","SESSION_SWR_OPTIONS","revalidateOnFocus","revalidateOnReconnect","shouldRetryOnError","onError","err","console","warn","SessionDataProvider","children","sessionDataUrl","isLoading","contextValue","sessionData","Provider","value","useSessionData","context","useSessionDataDirect"],"mappings":"AAAA,OAAOA,OACLC,aAAa,CACbC,UAAU,CAEVC,OAAO,KACF,OAAQ,AACf,QAAOC,WAAY,KAAM,CAsBzB,MAAMC,mBAAqBJ,cACzBK,WAeF,MAAMC,QAAU,MAAOC,MACrB,MAAMC,QAAuB,CAC3BC,QAAS,CACPC,OAAQ,kBACV,EACAC,MAAO,UACT,EAGA,GACE,
|
|
1
|
+
{"version":3,"sources":["../../src/core/SessionData.tsx"],"sourcesContent":["import React, {\n createContext,\n useContext,\n PropsWithChildren,\n useMemo,\n} from \"react\";\nimport useSWR from \"swr\";\n\n// Feature flag for enabling credentials in fetch requests\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\ndeclare const __ENABLE_FETCH_WITH_CREDENTIALS__: boolean | undefined;\n\ntype SessionUser = {\n firstName?: string;\n lastName?: string;\n email?: string;\n};\n\ntype SessionData = {\n user?: SessionUser;\n} | null;\n\ntype SessionDataContextType = {\n sessionData: SessionData;\n isLoading: boolean;\n error: Error | undefined;\n};\n\nconst SessionDataContext = createContext<SessionDataContextType | undefined>(\n undefined,\n);\n\ntype SessionDataProviderProps = PropsWithChildren<{\n sessionDataUrl?: string;\n}>;\n\n/**\n * fetcher - Retrieves session data from a backend API endpoint.\n *\n * Makes a JSON request to fetch current user session information. Optionally includes\n * credentials (cookies) based on the __ENABLE_FETCH_WITH_CREDENTIALS__ feature flag.\n * Handles \"not-found\" responses gracefully by returning null instead of throwing.\n * Validates response is JSON to prevent parsing errors.\n */\nconst fetcher = async (url: string): Promise<SessionData> => {\n const options: RequestInit = {\n headers: {\n accept: \"application/json\",\n },\n cache: \"no-cache\",\n };\n\n // Check if credentials should be included (feature flag)\n if (\n typeof __ENABLE_FETCH_WITH_CREDENTIALS__ !== \"undefined\" &&\n __ENABLE_FETCH_WITH_CREDENTIALS__\n ) {\n options.credentials = \"include\";\n }\n\n const res = await fetch(url, options);\n\n if (!res.ok) {\n throw new Error(\"Failed to fetch session data\");\n }\n\n const contentType = res.headers.get(\"content-type\");\n if (!contentType?.includes(\"application/json\")) {\n throw new Error(\"Session endpoint is not serving json\");\n }\n\n const data = await res.json();\n\n // Handle \"not found\" error gracefully\n if (data.error === \"not-found\") {\n return null;\n }\n\n return data;\n};\n\nconst SESSION_SWR_OPTIONS = {\n revalidateOnFocus: false,\n revalidateOnReconnect: false,\n shouldRetryOnError: false,\n onError: (err: Error) => {\n console.warn(\"Could not fetch session data due to error:\", err);\n },\n};\n\n/**\n * SessionDataProvider - Context provider for managing user session data throughout the app.\n *\n * Fetches and caches session data using SWR (stale-while-revalidate) from the provided URL.\n * Exposes session data, loading state, and errors through context. Use this at the app root\n * level and access via useSessionData() hook in child components. Disables auto-revalidation\n * to avoid excessive network requests - session data is fetched once on mount.\n */\nexport const SessionDataProvider = ({\n children,\n sessionDataUrl,\n}: SessionDataProviderProps) => {\n const { data, error, isLoading } = useSWR<SessionData>(\n sessionDataUrl || null,\n fetcher,\n SESSION_SWR_OPTIONS,\n );\n\n const contextValue = useMemo(\n () => ({\n sessionData: data ?? null,\n isLoading,\n error,\n }),\n [data, isLoading, error],\n );\n\n return (\n <SessionDataContext.Provider value={contextValue}>\n {children}\n </SessionDataContext.Provider>\n );\n};\n\n/**\n * useSessionData - Hook to access session data from SessionDataProvider context.\n *\n * Returns the current user session data, loading state, and any errors from the provider.\n * Must be used within a SessionDataProvider or will throw an error. Use this hook in\n * components that need to read or display user session information.\n */\nexport const useSessionData = () => {\n const context = useContext(SessionDataContext);\n if (context === undefined) {\n throw new Error(\"useSessionData must be used within SessionDataProvider\");\n }\n return context;\n};\n\n/**\n * useSessionDataDirect - Direct session data hook without requiring a provider (legacy).\n *\n * Fetches session data using SWR directly, bypassing the context provider pattern.\n * Provided for backward compatibility with code that doesn't use SessionDataProvider.\n * For new code, prefer using SessionDataProvider + useSessionData() for better\n * performance and centralized session management.\n */\nexport const useSessionDataDirect = (sessionDataUrl?: string) => {\n const { data, error, isLoading } = useSWR<SessionData>(\n sessionDataUrl || null,\n fetcher,\n SESSION_SWR_OPTIONS,\n );\n\n return {\n sessionData: data ?? null,\n isLoading,\n error,\n };\n};\n"],"names":["React","createContext","useContext","useMemo","useSWR","SessionDataContext","undefined","fetcher","url","options","headers","accept","cache","credentials","res","fetch","ok","Error","contentType","get","includes","data","json","error","SESSION_SWR_OPTIONS","revalidateOnFocus","revalidateOnReconnect","shouldRetryOnError","onError","err","console","warn","SessionDataProvider","children","sessionDataUrl","isLoading","contextValue","sessionData","Provider","value","useSessionData","context","useSessionDataDirect"],"mappings":"AAAA,OAAOA,OACLC,aAAa,CACbC,UAAU,CAEVC,OAAO,KACF,OAAQ,AACf,QAAOC,WAAY,KAAM,CAsBzB,MAAMC,mBAAqBJ,cACzBK,WAeF,MAAMC,QAAU,MAAOC,MACrB,MAAMC,QAAuB,CAC3BC,QAAS,CACPC,OAAQ,kBACV,EACAC,MAAO,UACT,EAGA,GACE,OAgGD,QAhG8C,aAgG9C,MA9FC,CACAH,QAAQI,WAAW,CAAG,SACxB,CAEA,MAAMC,IAAM,MAAMC,MAAMP,IAAKC,SAE7B,GAAI,CAACK,IAAIE,EAAE,CAAE,CACX,MAAM,IAAIC,MAAM,+BAClB,CAEA,MAAMC,YAAcJ,IAAIJ,OAAO,CAACS,GAAG,CAAC,gBACpC,GAAI,CAACD,aAAaE,SAAS,oBAAqB,CAC9C,MAAM,IAAIH,MAAM,uCAClB,CAEA,MAAMI,KAAO,MAAMP,IAAIQ,IAAI,GAG3B,GAAID,KAAKE,KAAK,GAAK,YAAa,CAC9B,OAAO,IACT,CAEA,OAAOF,IACT,EAEA,MAAMG,oBAAsB,CAC1BC,kBAAmB,MACnBC,sBAAuB,MACvBC,mBAAoB,MACpBC,QAAS,AAACC,MACRC,QAAQC,IAAI,CAAC,6CAA8CF,IAC7D,CACF,CAUA,QAAO,MAAMG,oBAAsB,CAAC,CAClCC,QAAQ,CACRC,cAAc,CACW,IACzB,KAAM,CAAEb,IAAI,CAAEE,KAAK,CAAEY,SAAS,CAAE,CAAG/B,OACjC8B,gBAAkB,KAClB3B,QACAiB,qBAGF,MAAMY,aAAejC,QACnB,IAAO,CAAA,CACLkC,YAAahB,MAAQ,KACrBc,UACAZ,KACF,CAAA,EACA,CAACF,KAAMc,UAAWZ,MAAM,EAG1B,OACE,oBAAClB,mBAAmBiC,QAAQ,EAACC,MAAOH,cACjCH,SAGP,CAAE,AASF,QAAO,MAAMO,eAAiB,KAC5B,MAAMC,QAAUvC,WAAWG,oBAC3B,GAAIoC,UAAYnC,UAAW,CACzB,MAAM,IAAIW,MAAM,yDAClB,CACA,OAAOwB,OACT,CAAE,AAUF,QAAO,MAAMC,qBAAuB,AAACR,iBACnC,KAAM,CAAEb,IAAI,CAAEE,KAAK,CAAEY,SAAS,CAAE,CAAG/B,OACjC8B,gBAAkB,KAClB3B,QACAiB,qBAGF,MAAO,CACLa,YAAahB,MAAQ,KACrBc,UACAZ,KACF,CACF,CAAE"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{describe,it,expect,beforeEach,afterEach,vi}from"vitest";import{renderHook,act}from"@testing-library/react";import{useThemedScrollpoints}from"./use-themed-scrollpoints";describe("useThemedScrollpoints",()=>{let observerCallback;let mockObserve;let mockUnobserve;let mockDisconnect;let originalIntersectionObserver;let originalRequestAnimationFrame;beforeEach(()=>{vi.useFakeTimers();originalIntersectionObserver=global.IntersectionObserver;originalRequestAnimationFrame=global.requestAnimationFrame;mockObserve=vi.fn();mockUnobserve=vi.fn();mockDisconnect=vi.fn();global.IntersectionObserver=vi.fn(callback
|
|
1
|
+
import{describe,it,expect,beforeEach,afterEach,vi}from"vitest";import{renderHook,act}from"@testing-library/react";import{useThemedScrollpoints}from"./use-themed-scrollpoints";describe("useThemedScrollpoints",()=>{let observerCallback;let mockObserve;let mockUnobserve;let mockDisconnect;let originalIntersectionObserver;let originalRequestAnimationFrame;beforeEach(()=>{vi.useFakeTimers();originalIntersectionObserver=global.IntersectionObserver;originalRequestAnimationFrame=global.requestAnimationFrame;mockObserve=vi.fn();mockUnobserve=vi.fn();mockDisconnect=vi.fn();global.IntersectionObserver=vi.fn(function(callback){observerCallback=callback;return{observe:mockObserve,unobserve:mockUnobserve,disconnect:mockDisconnect}});global.requestAnimationFrame=vi.fn(cb=>{cb(0);return 0})});afterEach(()=>{vi.clearAllMocks();vi.useRealTimers();document.body.innerHTML="";global.IntersectionObserver=originalIntersectionObserver;global.requestAnimationFrame=originalRequestAnimationFrame});it("returns first scrollpoint className on mount",()=>{const elem1=document.createElement("div");elem1.id="zone1";elem1.getBoundingClientRect=vi.fn().mockReturnValue({top:0,bottom:200});const elem2=document.createElement("div");elem2.id="zone2";elem2.getBoundingClientRect=vi.fn().mockReturnValue({top:200,bottom:400});document.body.appendChild(elem1);document.body.appendChild(elem2);const scrollpoints=[{id:"zone1",className:"theme-light"},{id:"zone2",className:"theme-dark"}];const{result}=renderHook(()=>useThemedScrollpoints(scrollpoints));expect(result.current).toBe("");act(()=>{vi.runAllTimers()});expect(result.current).toBe("theme-light")});it("returns empty string when no scrollpoints provided",()=>{const{result}=renderHook(()=>useThemedScrollpoints([]));expect(result.current).toBe("")});it("clears activeClassName when scrollpoints changes from non-empty to empty",()=>{const elem=document.createElement("div");elem.id="zone1";elem.getBoundingClientRect=vi.fn().mockReturnValue({top:0,bottom:200});document.body.appendChild(elem);const{result,rerender}=renderHook(({scrollpoints})=>useThemedScrollpoints(scrollpoints),{initialProps:{scrollpoints:[{id:"zone1",className:"theme-light"}]}});act(()=>{vi.runAllTimers()});expect(result.current).toBe("theme-light");rerender({scrollpoints:[]});expect(result.current).toBe("")});it("does not create IntersectionObserver when no scrollpoints provided",()=>{renderHook(()=>useThemedScrollpoints([]));expect(IntersectionObserver).not.toHaveBeenCalled()});it("creates IntersectionObserver with correct config",()=>{const scrollpoints=[{id:"zone1",className:"theme-light"}];const elem=document.createElement("div");elem.id="zone1";document.body.appendChild(elem);renderHook(()=>useThemedScrollpoints(scrollpoints));expect(IntersectionObserver).toHaveBeenCalledWith(expect.any(Function),expect.objectContaining({rootMargin:"-64px 0px 0px 0px",threshold:0}))});it("observes all scrollpoint elements that exist in DOM",()=>{const elem1=document.createElement("div");elem1.id="zone1";const elem2=document.createElement("div");elem2.id="zone2";document.body.appendChild(elem1);document.body.appendChild(elem2);const scrollpoints=[{id:"zone1",className:"theme-light"},{id:"zone2",className:"theme-dark"}];renderHook(()=>useThemedScrollpoints(scrollpoints));expect(mockObserve).toHaveBeenCalledTimes(2);expect(mockObserve).toHaveBeenCalledWith(elem1);expect(mockObserve).toHaveBeenCalledWith(elem2)});it("logs warning for missing DOM elements",()=>{const consoleWarn=vi.spyOn(console,"warn").mockImplementation(()=>{});const scrollpoints=[{id:"non-existent",className:"theme"}];renderHook(()=>useThemedScrollpoints(scrollpoints));expect(consoleWarn).toHaveBeenCalledWith(expect.stringContaining('Element with id "non-existent" not found'));consoleWarn.mockRestore()});it("observes existing elements and warns about missing ones",()=>{const consoleWarn=vi.spyOn(console,"warn").mockImplementation(()=>{});const elem=document.createElement("div");elem.id="zone1";document.body.appendChild(elem);const scrollpoints=[{id:"zone1",className:"theme-light"},{id:"missing",className:"theme-dark"}];renderHook(()=>useThemedScrollpoints(scrollpoints));expect(mockObserve).toHaveBeenCalledTimes(1);expect(mockObserve).toHaveBeenCalledWith(elem);expect(consoleWarn).toHaveBeenCalledWith(expect.stringContaining('Element with id "missing" not found'));consoleWarn.mockRestore()});it("updates className when intersection occurs",()=>{const elem1=document.createElement("div");elem1.id="zone1";elem1.getBoundingClientRect=vi.fn().mockReturnValue({top:0,bottom:200});const elem2=document.createElement("div");elem2.id="zone2";elem2.getBoundingClientRect=vi.fn().mockReturnValue({top:50,bottom:250});document.body.appendChild(elem1);document.body.appendChild(elem2);const scrollpoints=[{id:"zone1",className:"theme-light"},{id:"zone2",className:"theme-dark"}];const{result}=renderHook(()=>useThemedScrollpoints(scrollpoints));act(()=>{vi.runAllTimers()});expect(result.current).toBe("theme-dark");act(()=>{observerCallback([{target:elem1,isIntersecting:true,boundingClientRect:{top:60,bottom:260,left:0,right:0,x:0,y:60,width:0,height:200}},{target:elem2,isIntersecting:true,boundingClientRect:{top:100,bottom:300,left:0,right:0,x:0,y:100,width:0,height:200}}],{})});expect(result.current).toBe("theme-light")});it("does not update className if same scrollpoint intersects again",()=>{const elem=document.createElement("div");elem.id="zone1";elem.getBoundingClientRect=vi.fn().mockReturnValue({top:0,bottom:200});document.body.appendChild(elem);const scrollpoints=[{id:"zone1",className:"theme-light"}];const{result}=renderHook(()=>useThemedScrollpoints(scrollpoints));act(()=>{vi.runAllTimers()});expect(result.current).toBe("theme-light");const renderCount=result.current;observerCallback([{target:elem,isIntersecting:true,boundingClientRect:{top:0,bottom:200,left:0,right:0,x:0,y:0,width:0,height:200}}],{});expect(result.current).toBe(renderCount)});it("ignores non-intersecting entries",()=>{const elem1=document.createElement("div");elem1.id="zone1";elem1.getBoundingClientRect=vi.fn().mockReturnValue({top:0,bottom:200});const elem2=document.createElement("div");elem2.id="zone2";elem2.getBoundingClientRect=vi.fn().mockReturnValue({top:200,bottom:400});document.body.appendChild(elem1);document.body.appendChild(elem2);const scrollpoints=[{id:"zone1",className:"theme-light"},{id:"zone2",className:"theme-dark"}];const{result}=renderHook(()=>useThemedScrollpoints(scrollpoints));act(()=>{vi.runAllTimers()});expect(result.current).toBe("theme-light");observerCallback([{target:elem2,isIntersecting:false}],{});expect(result.current).toBe("theme-light")});it("uses closest element to header when multiple entries intersect",()=>{const elem1=document.createElement("div");elem1.id="zone1";elem1.getBoundingClientRect=vi.fn().mockReturnValue({top:0,bottom:50});const elem2=document.createElement("div");elem2.id="zone2";elem2.getBoundingClientRect=vi.fn().mockReturnValue({top:60,bottom:200});const elem3=document.createElement("div");elem3.id="zone3";elem3.getBoundingClientRect=vi.fn().mockReturnValue({top:10,bottom:100});document.body.appendChild(elem1);document.body.appendChild(elem2);document.body.appendChild(elem3);const scrollpoints=[{id:"zone1",className:"theme-light"},{id:"zone2",className:"theme-dark"},{id:"zone3",className:"theme-blue"}];const{result}=renderHook(()=>useThemedScrollpoints(scrollpoints));act(()=>{vi.runAllTimers()});expect(result.current).toBe("theme-dark");act(()=>{observerCallback([{target:elem1,isIntersecting:false,boundingClientRect:{top:0,bottom:50,left:0,right:0,x:0,y:0,width:0,height:50}},{target:elem2,isIntersecting:true,boundingClientRect:{top:100,bottom:300,left:0,right:0,x:0,y:100,width:0,height:200}},{target:elem3,isIntersecting:true,boundingClientRect:{top:62,bottom:152,left:0,right:0,x:0,y:62,width:0,height:90}}],{})});expect(result.current).toBe("theme-blue")});it("disconnects observer on unmount",()=>{const elem=document.createElement("div");elem.id="zone1";document.body.appendChild(elem);const scrollpoints=[{id:"zone1",className:"theme-light"}];const{unmount}=renderHook(()=>useThemedScrollpoints(scrollpoints));unmount();expect(mockDisconnect).toHaveBeenCalled()});it("recreates observer when scrollpoints change",()=>{const elem1=document.createElement("div");elem1.id="zone1";const elem2=document.createElement("div");elem2.id="zone2";document.body.appendChild(elem1);document.body.appendChild(elem2);const{rerender}=renderHook(({scrollpoints})=>useThemedScrollpoints(scrollpoints),{initialProps:{scrollpoints:[{id:"zone1",className:"theme-light"}]}});expect(IntersectionObserver).toHaveBeenCalledTimes(1);expect(mockObserve).toHaveBeenCalledTimes(1);rerender({scrollpoints:[{id:"zone1",className:"theme-light"},{id:"zone2",className:"theme-dark"}]});expect(mockDisconnect).toHaveBeenCalledTimes(1);expect(IntersectionObserver).toHaveBeenCalledTimes(2);expect(mockObserve).toHaveBeenCalledTimes(3)});it("uses requestAnimationFrame for state updates",()=>{const elem=document.createElement("div");elem.id="zone1";document.body.appendChild(elem);const scrollpoints=[{id:"zone1",className:"theme-light"},{id:"zone2",className:"theme-dark"}];renderHook(()=>useThemedScrollpoints(scrollpoints));const rafSpy=vi.spyOn(global,"requestAnimationFrame");observerCallback([{target:elem,isIntersecting:true}],{});expect(rafSpy).toHaveBeenCalled();rafSpy.mockRestore()})});
|
|
2
2
|
//# sourceMappingURL=use-themed-scrollpoints.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/core/hooks/use-themed-scrollpoints.test.ts"],"sourcesContent":["/**\n * @vitest-environment jsdom\n */\n\nimport { describe, it, expect, beforeEach, afterEach, vi } from \"vitest\";\nimport { renderHook, act } from \"@testing-library/react\";\nimport { useThemedScrollpoints } from \"./use-themed-scrollpoints\";\n\ndescribe(\"useThemedScrollpoints\", () => {\n let observerCallback: IntersectionObserverCallback;\n let mockObserve: ReturnType<typeof vi.fn>;\n let mockUnobserve: ReturnType<typeof vi.fn>;\n let mockDisconnect: ReturnType<typeof vi.fn>;\n let originalIntersectionObserver: typeof IntersectionObserver;\n let originalRequestAnimationFrame: typeof requestAnimationFrame;\n\n beforeEach(() => {\n vi.useFakeTimers();\n\n // Save originals to prevent test pollution\n originalIntersectionObserver = global.IntersectionObserver;\n originalRequestAnimationFrame = global.requestAnimationFrame;\n\n // Mock IntersectionObserver\n mockObserve = vi.fn();\n mockUnobserve = vi.fn();\n mockDisconnect = vi.fn();\n\n global.IntersectionObserver = vi.fn((callback) => {\n observerCallback = callback;\n return {\n observe: mockObserve,\n unobserve: mockUnobserve,\n disconnect: mockDisconnect,\n };\n }) as unknown as typeof IntersectionObserver;\n\n // Mock requestAnimationFrame\n global.requestAnimationFrame = vi.fn((cb) => {\n cb(0);\n return 0;\n });\n });\n\n afterEach(() => {\n vi.clearAllMocks();\n vi.useRealTimers();\n document.body.innerHTML = \"\";\n // Restore originals to prevent test pollution\n global.IntersectionObserver = originalIntersectionObserver;\n global.requestAnimationFrame = originalRequestAnimationFrame;\n });\n\n it(\"returns first scrollpoint className on mount\", () => {\n // Create DOM elements\n const elem1 = document.createElement(\"div\");\n elem1.id = \"zone1\";\n elem1.getBoundingClientRect = vi\n .fn()\n .mockReturnValue({ top: 0, bottom: 200 });\n const elem2 = document.createElement(\"div\");\n elem2.id = \"zone2\";\n elem2.getBoundingClientRect = vi\n .fn()\n .mockReturnValue({ top: 200, bottom: 400 });\n document.body.appendChild(elem1);\n document.body.appendChild(elem2);\n\n const scrollpoints = [\n { id: \"zone1\", className: \"theme-light\" },\n { id: \"zone2\", className: \"theme-dark\" },\n ];\n\n const { result } = renderHook(() => useThemedScrollpoints(scrollpoints));\n\n // Initial state is empty\n expect(result.current).toBe(\"\");\n\n // Advance timer to trigger initial check\n act(() => {\n vi.runAllTimers();\n });\n\n // Should now have the first scrollpoint's className\n expect(result.current).toBe(\"theme-light\");\n });\n\n it(\"returns empty string when no scrollpoints provided\", () => {\n const { result } = renderHook(() => useThemedScrollpoints([]));\n expect(result.current).toBe(\"\");\n });\n\n it(\"clears activeClassName when scrollpoints changes from non-empty to empty\", () => {\n const elem = document.createElement(\"div\");\n elem.id = \"zone1\";\n elem.getBoundingClientRect = vi\n .fn()\n .mockReturnValue({ top: 0, bottom: 200 });\n document.body.appendChild(elem);\n\n const { result, rerender } = renderHook(\n ({ scrollpoints }) => useThemedScrollpoints(scrollpoints),\n {\n initialProps: {\n scrollpoints: [{ id: \"zone1\", className: \"theme-light\" }],\n },\n },\n );\n\n // Wait for initial check\n act(() => {\n vi.runAllTimers();\n });\n expect(result.current).toBe(\"theme-light\");\n\n // Change to empty scrollpoints\n rerender({ scrollpoints: [] });\n\n // Should clear the className\n expect(result.current).toBe(\"\");\n });\n\n it(\"does not create IntersectionObserver when no scrollpoints provided\", () => {\n renderHook(() => useThemedScrollpoints([]));\n\n expect(IntersectionObserver).not.toHaveBeenCalled();\n });\n\n it(\"creates IntersectionObserver with correct config\", () => {\n const scrollpoints = [{ id: \"zone1\", className: \"theme-light\" }];\n\n // Create DOM element\n const elem = document.createElement(\"div\");\n elem.id = \"zone1\";\n document.body.appendChild(elem);\n\n renderHook(() => useThemedScrollpoints(scrollpoints));\n\n expect(IntersectionObserver).toHaveBeenCalledWith(\n expect.any(Function),\n expect.objectContaining({\n rootMargin: \"-64px 0px 0px 0px\",\n threshold: 0,\n }),\n );\n });\n\n it(\"observes all scrollpoint elements that exist in DOM\", () => {\n // Create DOM elements\n const elem1 = document.createElement(\"div\");\n elem1.id = \"zone1\";\n const elem2 = document.createElement(\"div\");\n elem2.id = \"zone2\";\n document.body.appendChild(elem1);\n document.body.appendChild(elem2);\n\n const scrollpoints = [\n { id: \"zone1\", className: \"theme-light\" },\n { id: \"zone2\", className: \"theme-dark\" },\n ];\n\n renderHook(() => useThemedScrollpoints(scrollpoints));\n\n expect(mockObserve).toHaveBeenCalledTimes(2);\n expect(mockObserve).toHaveBeenCalledWith(elem1);\n expect(mockObserve).toHaveBeenCalledWith(elem2);\n });\n\n it(\"logs warning for missing DOM elements\", () => {\n const consoleWarn = vi.spyOn(console, \"warn\").mockImplementation(() => {});\n\n const scrollpoints = [{ id: \"non-existent\", className: \"theme\" }];\n\n renderHook(() => useThemedScrollpoints(scrollpoints));\n\n expect(consoleWarn).toHaveBeenCalledWith(\n expect.stringContaining('Element with id \"non-existent\" not found'),\n );\n\n consoleWarn.mockRestore();\n });\n\n it(\"observes existing elements and warns about missing ones\", () => {\n const consoleWarn = vi.spyOn(console, \"warn\").mockImplementation(() => {});\n\n // Create only one element\n const elem = document.createElement(\"div\");\n elem.id = \"zone1\";\n document.body.appendChild(elem);\n\n const scrollpoints = [\n { id: \"zone1\", className: \"theme-light\" },\n { id: \"missing\", className: \"theme-dark\" },\n ];\n\n renderHook(() => useThemedScrollpoints(scrollpoints));\n\n expect(mockObserve).toHaveBeenCalledTimes(1);\n expect(mockObserve).toHaveBeenCalledWith(elem);\n expect(consoleWarn).toHaveBeenCalledWith(\n expect.stringContaining('Element with id \"missing\" not found'),\n );\n\n consoleWarn.mockRestore();\n });\n\n it(\"updates className when intersection occurs\", () => {\n // Create DOM elements\n const elem1 = document.createElement(\"div\");\n elem1.id = \"zone1\";\n elem1.getBoundingClientRect = vi\n .fn()\n .mockReturnValue({ top: 0, bottom: 200 });\n const elem2 = document.createElement(\"div\");\n elem2.id = \"zone2\";\n elem2.getBoundingClientRect = vi\n .fn()\n .mockReturnValue({ top: 50, bottom: 250 });\n document.body.appendChild(elem1);\n document.body.appendChild(elem2);\n\n const scrollpoints = [\n { id: \"zone1\", className: \"theme-light\" },\n { id: \"zone2\", className: \"theme-dark\" },\n ];\n\n const { result } = renderHook(() => useThemedScrollpoints(scrollpoints));\n\n // Wait for initial check\n // elem2 (top=50) is closer to header (64) than elem1 (top=0)\n // distance for elem1: |0 - 64| = 64\n // distance for elem2: |50 - 64| = 14\n act(() => {\n vi.runAllTimers();\n });\n expect(result.current).toBe(\"theme-dark\");\n\n // Simulate scrolling - elem1 moves closer to header position\n act(() => {\n observerCallback(\n [\n {\n target: elem1,\n isIntersecting: true,\n boundingClientRect: {\n top: 60,\n bottom: 260,\n left: 0,\n right: 0,\n x: 0,\n y: 60,\n width: 0,\n height: 200,\n },\n } as unknown as IntersectionObserverEntry,\n {\n target: elem2,\n isIntersecting: true,\n boundingClientRect: {\n top: 100,\n bottom: 300,\n left: 0,\n right: 0,\n x: 0,\n y: 100,\n width: 0,\n height: 200,\n },\n } as unknown as IntersectionObserverEntry,\n ],\n {} as IntersectionObserver,\n );\n });\n\n // elem1 is now closer to header (distance=4 vs elem2 distance=36)\n expect(result.current).toBe(\"theme-light\");\n });\n\n it(\"does not update className if same scrollpoint intersects again\", () => {\n const elem = document.createElement(\"div\");\n elem.id = \"zone1\";\n elem.getBoundingClientRect = vi\n .fn()\n .mockReturnValue({ top: 0, bottom: 200 });\n document.body.appendChild(elem);\n\n const scrollpoints = [{ id: \"zone1\", className: \"theme-light\" }];\n\n const { result } = renderHook(() => useThemedScrollpoints(scrollpoints));\n\n // Wait for initial check\n act(() => {\n vi.runAllTimers();\n });\n expect(result.current).toBe(\"theme-light\");\n\n // Simulate intersection with same element\n const renderCount = result.current;\n observerCallback(\n [\n {\n target: elem,\n isIntersecting: true,\n boundingClientRect: {\n top: 0,\n bottom: 200,\n left: 0,\n right: 0,\n x: 0,\n y: 0,\n width: 0,\n height: 200,\n },\n } as unknown as IntersectionObserverEntry,\n ],\n {} as IntersectionObserver,\n );\n\n // Should not trigger re-render (className unchanged)\n expect(result.current).toBe(renderCount);\n });\n\n it(\"ignores non-intersecting entries\", () => {\n const elem1 = document.createElement(\"div\");\n elem1.id = \"zone1\";\n elem1.getBoundingClientRect = vi\n .fn()\n .mockReturnValue({ top: 0, bottom: 200 });\n const elem2 = document.createElement(\"div\");\n elem2.id = \"zone2\";\n elem2.getBoundingClientRect = vi\n .fn()\n .mockReturnValue({ top: 200, bottom: 400 });\n document.body.appendChild(elem1);\n document.body.appendChild(elem2);\n\n const scrollpoints = [\n { id: \"zone1\", className: \"theme-light\" },\n { id: \"zone2\", className: \"theme-dark\" },\n ];\n\n const { result } = renderHook(() => useThemedScrollpoints(scrollpoints));\n\n // Wait for initial check\n act(() => {\n vi.runAllTimers();\n });\n expect(result.current).toBe(\"theme-light\");\n\n // Simulate non-intersecting entry\n observerCallback(\n [\n {\n target: elem2,\n isIntersecting: false,\n } as unknown as IntersectionObserverEntry,\n ],\n {} as IntersectionObserver,\n );\n\n // Should remain unchanged\n expect(result.current).toBe(\"theme-light\");\n });\n\n it(\"uses closest element to header when multiple entries intersect\", () => {\n const elem1 = document.createElement(\"div\");\n elem1.id = \"zone1\";\n elem1.getBoundingClientRect = vi\n .fn()\n .mockReturnValue({ top: 0, bottom: 50 });\n const elem2 = document.createElement(\"div\");\n elem2.id = \"zone2\";\n // zone2 top is at 60, which is closer to HEADER_HEIGHT (64) than zone3\n elem2.getBoundingClientRect = vi\n .fn()\n .mockReturnValue({ top: 60, bottom: 200 });\n const elem3 = document.createElement(\"div\");\n elem3.id = \"zone3\";\n elem3.getBoundingClientRect = vi\n .fn()\n .mockReturnValue({ top: 10, bottom: 100 });\n document.body.appendChild(elem1);\n document.body.appendChild(elem2);\n document.body.appendChild(elem3);\n\n const scrollpoints = [\n { id: \"zone1\", className: \"theme-light\" },\n { id: \"zone2\", className: \"theme-dark\" },\n { id: \"zone3\", className: \"theme-blue\" },\n ];\n\n const { result } = renderHook(() => useThemedScrollpoints(scrollpoints));\n\n // Wait for initial check\n // elem2 (top=60) is closest to header (64) with distance=4\n // elem3 (top=10) has distance=54\n // elem1 (top=0) doesn't contain header (bottom=50 < 64)\n act(() => {\n vi.runAllTimers();\n });\n expect(result.current).toBe(\"theme-dark\");\n\n // Simulate scrolling where elem3 becomes closest to header\n act(() => {\n observerCallback(\n [\n {\n target: elem1,\n isIntersecting: false,\n boundingClientRect: {\n top: 0,\n bottom: 50,\n left: 0,\n right: 0,\n x: 0,\n y: 0,\n width: 0,\n height: 50,\n },\n } as unknown as IntersectionObserverEntry,\n {\n target: elem2,\n isIntersecting: true,\n boundingClientRect: {\n top: 100,\n bottom: 300,\n left: 0,\n right: 0,\n x: 0,\n y: 100,\n width: 0,\n height: 200,\n },\n } as unknown as IntersectionObserverEntry,\n {\n target: elem3,\n isIntersecting: true,\n boundingClientRect: {\n top: 62,\n bottom: 152,\n left: 0,\n right: 0,\n x: 0,\n y: 62,\n width: 0,\n height: 90,\n },\n } as unknown as IntersectionObserverEntry,\n ],\n {} as IntersectionObserver,\n );\n });\n\n // elem3 is now closest to HEADER_HEIGHT (64)\n // distance for elem2: |100 - 64| = 36\n // distance for elem3: |62 - 64| = 2\n expect(result.current).toBe(\"theme-blue\");\n });\n\n it(\"disconnects observer on unmount\", () => {\n const elem = document.createElement(\"div\");\n elem.id = \"zone1\";\n document.body.appendChild(elem);\n\n const scrollpoints = [{ id: \"zone1\", className: \"theme-light\" }];\n\n const { unmount } = renderHook(() => useThemedScrollpoints(scrollpoints));\n\n unmount();\n\n expect(mockDisconnect).toHaveBeenCalled();\n });\n\n it(\"recreates observer when scrollpoints change\", () => {\n const elem1 = document.createElement(\"div\");\n elem1.id = \"zone1\";\n const elem2 = document.createElement(\"div\");\n elem2.id = \"zone2\";\n document.body.appendChild(elem1);\n document.body.appendChild(elem2);\n\n const { rerender } = renderHook(\n ({ scrollpoints }) => useThemedScrollpoints(scrollpoints),\n {\n initialProps: {\n scrollpoints: [{ id: \"zone1\", className: \"theme-light\" }],\n },\n },\n );\n\n expect(IntersectionObserver).toHaveBeenCalledTimes(1);\n expect(mockObserve).toHaveBeenCalledTimes(1);\n\n // Change scrollpoints\n rerender({\n scrollpoints: [\n { id: \"zone1\", className: \"theme-light\" },\n { id: \"zone2\", className: \"theme-dark\" },\n ],\n });\n\n // Should disconnect old observer and create new one\n expect(mockDisconnect).toHaveBeenCalledTimes(1);\n expect(IntersectionObserver).toHaveBeenCalledTimes(2);\n expect(mockObserve).toHaveBeenCalledTimes(3); // 1 from first render + 2 from second\n });\n\n it(\"uses requestAnimationFrame for state updates\", () => {\n const elem = document.createElement(\"div\");\n elem.id = \"zone1\";\n document.body.appendChild(elem);\n\n const scrollpoints = [\n { id: \"zone1\", className: \"theme-light\" },\n { id: \"zone2\", className: \"theme-dark\" },\n ];\n\n renderHook(() => useThemedScrollpoints(scrollpoints));\n\n const rafSpy = vi.spyOn(global, \"requestAnimationFrame\");\n\n // Simulate intersection\n observerCallback(\n [\n {\n target: elem,\n isIntersecting: true,\n } as unknown as IntersectionObserverEntry,\n ],\n {} as IntersectionObserver,\n );\n\n expect(rafSpy).toHaveBeenCalled();\n\n rafSpy.mockRestore();\n });\n});\n"],"names":["describe","it","expect","beforeEach","afterEach","vi","renderHook","act","useThemedScrollpoints","observerCallback","mockObserve","mockUnobserve","mockDisconnect","originalIntersectionObserver","originalRequestAnimationFrame","useFakeTimers","global","IntersectionObserver","requestAnimationFrame","fn","callback","observe","unobserve","disconnect","cb","clearAllMocks","useRealTimers","document","body","innerHTML","elem1","createElement","id","getBoundingClientRect","mockReturnValue","top","bottom","elem2","appendChild","scrollpoints","className","result","current","toBe","runAllTimers","elem","rerender","initialProps","not","toHaveBeenCalled","toHaveBeenCalledWith","any","Function","objectContaining","rootMargin","threshold","toHaveBeenCalledTimes","consoleWarn","spyOn","console","mockImplementation","stringContaining","mockRestore","target","isIntersecting","boundingClientRect","left","right","x","y","width","height","renderCount","elem3","unmount","rafSpy"],"mappings":"AAIA,OAASA,QAAQ,CAAEC,EAAE,CAAEC,MAAM,CAAEC,UAAU,CAAEC,SAAS,CAAEC,EAAE,KAAQ,QAAS,AACzE,QAASC,UAAU,CAAEC,GAAG,KAAQ,wBAAyB,AACzD,QAASC,qBAAqB,KAAQ,2BAA4B,CAElER,SAAS,wBAAyB,KAChC,IAAIS,iBACJ,IAAIC,YACJ,IAAIC,cACJ,IAAIC,eACJ,IAAIC,6BACJ,IAAIC,8BAEJX,WAAW,KACTE,GAAGU,aAAa,GAGhBF,6BAA+BG,OAAOC,oBAAoB,CAC1DH,8BAAgCE,OAAOE,qBAAqB,CAG5DR,YAAcL,GAAGc,EAAE,GACnBR,cAAgBN,GAAGc,EAAE,GACrBP,eAAiBP,GAAGc,EAAE,EAEtBH,CAAAA,OAAOC,oBAAoB,CAAGZ,GAAGc,EAAE,CAAC,AAACC,WACnCX,iBAAmBW,SACnB,MAAO,CACLC,QAASX,YACTY,UAAWX,cACXY,WAAYX,cACd,CACF,EAGAI,CAAAA,OAAOE,qBAAqB,CAAGb,GAAGc,EAAE,CAAC,AAACK,KACpCA,GAAG,GACH,OAAO,CACT,EACF,GAEApB,UAAU,KACRC,GAAGoB,aAAa,GAChBpB,GAAGqB,aAAa,EAChBC,CAAAA,SAASC,IAAI,CAACC,SAAS,CAAG,EAE1Bb,CAAAA,OAAOC,oBAAoB,CAAGJ,4BAC9BG,CAAAA,OAAOE,qBAAqB,CAAGJ,6BACjC,GAEAb,GAAG,+CAAgD,KAEjD,MAAM6B,MAAQH,SAASI,aAAa,CAAC,MACrCD,CAAAA,MAAME,EAAE,CAAG,OACXF,CAAAA,MAAMG,qBAAqB,CAAG5B,GAC3Bc,EAAE,GACFe,eAAe,CAAC,CAAEC,IAAK,EAAGC,OAAQ,GAAI,GACzC,MAAMC,MAAQV,SAASI,aAAa,CAAC,MACrCM,CAAAA,MAAML,EAAE,CAAG,OACXK,CAAAA,MAAMJ,qBAAqB,CAAG5B,GAC3Bc,EAAE,GACFe,eAAe,CAAC,CAAEC,IAAK,IAAKC,OAAQ,GAAI,GAC3CT,SAASC,IAAI,CAACU,WAAW,CAACR,OAC1BH,SAASC,IAAI,CAACU,WAAW,CAACD,OAE1B,MAAME,aAAe,CACnB,CAAEP,GAAI,QAASQ,UAAW,aAAc,EACxC,CAAER,GAAI,QAASQ,UAAW,YAAa,EACxC,CAED,KAAM,CAAEC,MAAM,CAAE,CAAGnC,WAAW,IAAME,sBAAsB+B,eAG1DrC,OAAOuC,OAAOC,OAAO,EAAEC,IAAI,CAAC,IAG5BpC,IAAI,KACFF,GAAGuC,YAAY,EACjB,GAGA1C,OAAOuC,OAAOC,OAAO,EAAEC,IAAI,CAAC,cAC9B,GAEA1C,GAAG,qDAAsD,KACvD,KAAM,CAAEwC,MAAM,CAAE,CAAGnC,WAAW,IAAME,sBAAsB,EAAE,GAC5DN,OAAOuC,OAAOC,OAAO,EAAEC,IAAI,CAAC,GAC9B,GAEA1C,GAAG,2EAA4E,KAC7E,MAAM4C,KAAOlB,SAASI,aAAa,CAAC,MACpCc,CAAAA,KAAKb,EAAE,CAAG,OACVa,CAAAA,KAAKZ,qBAAqB,CAAG5B,GAC1Bc,EAAE,GACFe,eAAe,CAAC,CAAEC,IAAK,EAAGC,OAAQ,GAAI,GACzCT,SAASC,IAAI,CAACU,WAAW,CAACO,MAE1B,KAAM,CAAEJ,MAAM,CAAEK,QAAQ,CAAE,CAAGxC,WAC3B,CAAC,CAAEiC,YAAY,CAAE,GAAK/B,sBAAsB+B,cAC5C,CACEQ,aAAc,CACZR,aAAc,CAAC,CAAEP,GAAI,QAASQ,UAAW,aAAc,EAAE,AAC3D,CACF,GAIFjC,IAAI,KACFF,GAAGuC,YAAY,EACjB,GACA1C,OAAOuC,OAAOC,OAAO,EAAEC,IAAI,CAAC,eAG5BG,SAAS,CAAEP,aAAc,EAAE,AAAC,GAG5BrC,OAAOuC,OAAOC,OAAO,EAAEC,IAAI,CAAC,GAC9B,GAEA1C,GAAG,qEAAsE,KACvEK,WAAW,IAAME,sBAAsB,EAAE,GAEzCN,OAAOe,sBAAsB+B,GAAG,CAACC,gBAAgB,EACnD,GAEAhD,GAAG,mDAAoD,KACrD,MAAMsC,aAAe,CAAC,CAAEP,GAAI,QAASQ,UAAW,aAAc,EAAE,CAGhE,MAAMK,KAAOlB,SAASI,aAAa,CAAC,MACpCc,CAAAA,KAAKb,EAAE,CAAG,QACVL,SAASC,IAAI,CAACU,WAAW,CAACO,MAE1BvC,WAAW,IAAME,sBAAsB+B,eAEvCrC,OAAOe,sBAAsBiC,oBAAoB,CAC/ChD,OAAOiD,GAAG,CAACC,UACXlD,OAAOmD,gBAAgB,CAAC,CACtBC,WAAY,oBACZC,UAAW,CACb,GAEJ,GAEAtD,GAAG,sDAAuD,KAExD,MAAM6B,MAAQH,SAASI,aAAa,CAAC,MACrCD,CAAAA,MAAME,EAAE,CAAG,QACX,MAAMK,MAAQV,SAASI,aAAa,CAAC,MACrCM,CAAAA,MAAML,EAAE,CAAG,QACXL,SAASC,IAAI,CAACU,WAAW,CAACR,OAC1BH,SAASC,IAAI,CAACU,WAAW,CAACD,OAE1B,MAAME,aAAe,CACnB,CAAEP,GAAI,QAASQ,UAAW,aAAc,EACxC,CAAER,GAAI,QAASQ,UAAW,YAAa,EACxC,CAEDlC,WAAW,IAAME,sBAAsB+B,eAEvCrC,OAAOQ,aAAa8C,qBAAqB,CAAC,GAC1CtD,OAAOQ,aAAawC,oBAAoB,CAACpB,OACzC5B,OAAOQ,aAAawC,oBAAoB,CAACb,MAC3C,GAEApC,GAAG,wCAAyC,KAC1C,MAAMwD,YAAcpD,GAAGqD,KAAK,CAACC,QAAS,QAAQC,kBAAkB,CAAC,KAAO,GAExE,MAAMrB,aAAe,CAAC,CAAEP,GAAI,eAAgBQ,UAAW,OAAQ,EAAE,CAEjElC,WAAW,IAAME,sBAAsB+B,eAEvCrC,OAAOuD,aAAaP,oBAAoB,CACtChD,OAAO2D,gBAAgB,CAAC,6CAG1BJ,YAAYK,WAAW,EACzB,GAEA7D,GAAG,0DAA2D,KAC5D,MAAMwD,YAAcpD,GAAGqD,KAAK,CAACC,QAAS,QAAQC,kBAAkB,CAAC,KAAO,GAGxE,MAAMf,KAAOlB,SAASI,aAAa,CAAC,MACpCc,CAAAA,KAAKb,EAAE,CAAG,QACVL,SAASC,IAAI,CAACU,WAAW,CAACO,MAE1B,MAAMN,aAAe,CACnB,CAAEP,GAAI,QAASQ,UAAW,aAAc,EACxC,CAAER,GAAI,UAAWQ,UAAW,YAAa,EAC1C,CAEDlC,WAAW,IAAME,sBAAsB+B,eAEvCrC,OAAOQ,aAAa8C,qBAAqB,CAAC,GAC1CtD,OAAOQ,aAAawC,oBAAoB,CAACL,MACzC3C,OAAOuD,aAAaP,oBAAoB,CACtChD,OAAO2D,gBAAgB,CAAC,wCAG1BJ,YAAYK,WAAW,EACzB,GAEA7D,GAAG,6CAA8C,KAE/C,MAAM6B,MAAQH,SAASI,aAAa,CAAC,MACrCD,CAAAA,MAAME,EAAE,CAAG,OACXF,CAAAA,MAAMG,qBAAqB,CAAG5B,GAC3Bc,EAAE,GACFe,eAAe,CAAC,CAAEC,IAAK,EAAGC,OAAQ,GAAI,GACzC,MAAMC,MAAQV,SAASI,aAAa,CAAC,MACrCM,CAAAA,MAAML,EAAE,CAAG,OACXK,CAAAA,MAAMJ,qBAAqB,CAAG5B,GAC3Bc,EAAE,GACFe,eAAe,CAAC,CAAEC,IAAK,GAAIC,OAAQ,GAAI,GAC1CT,SAASC,IAAI,CAACU,WAAW,CAACR,OAC1BH,SAASC,IAAI,CAACU,WAAW,CAACD,OAE1B,MAAME,aAAe,CACnB,CAAEP,GAAI,QAASQ,UAAW,aAAc,EACxC,CAAER,GAAI,QAASQ,UAAW,YAAa,EACxC,CAED,KAAM,CAAEC,MAAM,CAAE,CAAGnC,WAAW,IAAME,sBAAsB+B,eAM1DhC,IAAI,KACFF,GAAGuC,YAAY,EACjB,GACA1C,OAAOuC,OAAOC,OAAO,EAAEC,IAAI,CAAC,cAG5BpC,IAAI,KACFE,iBACE,CACE,CACEsD,OAAQjC,MACRkC,eAAgB,KAChBC,mBAAoB,CAClB9B,IAAK,GACLC,OAAQ,IACR8B,KAAM,EACNC,MAAO,EACPC,EAAG,EACHC,EAAG,GACHC,MAAO,EACPC,OAAQ,GACV,CACF,EACA,CACER,OAAQ1B,MACR2B,eAAgB,KAChBC,mBAAoB,CAClB9B,IAAK,IACLC,OAAQ,IACR8B,KAAM,EACNC,MAAO,EACPC,EAAG,EACHC,EAAG,IACHC,MAAO,EACPC,OAAQ,GACV,CACF,EACD,CACD,CAAC,EAEL,GAGArE,OAAOuC,OAAOC,OAAO,EAAEC,IAAI,CAAC,cAC9B,GAEA1C,GAAG,iEAAkE,KACnE,MAAM4C,KAAOlB,SAASI,aAAa,CAAC,MACpCc,CAAAA,KAAKb,EAAE,CAAG,OACVa,CAAAA,KAAKZ,qBAAqB,CAAG5B,GAC1Bc,EAAE,GACFe,eAAe,CAAC,CAAEC,IAAK,EAAGC,OAAQ,GAAI,GACzCT,SAASC,IAAI,CAACU,WAAW,CAACO,MAE1B,MAAMN,aAAe,CAAC,CAAEP,GAAI,QAASQ,UAAW,aAAc,EAAE,CAEhE,KAAM,CAAEC,MAAM,CAAE,CAAGnC,WAAW,IAAME,sBAAsB+B,eAG1DhC,IAAI,KACFF,GAAGuC,YAAY,EACjB,GACA1C,OAAOuC,OAAOC,OAAO,EAAEC,IAAI,CAAC,eAG5B,MAAM6B,YAAc/B,OAAOC,OAAO,CAClCjC,iBACE,CACE,CACEsD,OAAQlB,KACRmB,eAAgB,KAChBC,mBAAoB,CAClB9B,IAAK,EACLC,OAAQ,IACR8B,KAAM,EACNC,MAAO,EACPC,EAAG,EACHC,EAAG,EACHC,MAAO,EACPC,OAAQ,GACV,CACF,EACD,CACD,CAAC,GAIHrE,OAAOuC,OAAOC,OAAO,EAAEC,IAAI,CAAC6B,YAC9B,GAEAvE,GAAG,mCAAoC,KACrC,MAAM6B,MAAQH,SAASI,aAAa,CAAC,MACrCD,CAAAA,MAAME,EAAE,CAAG,OACXF,CAAAA,MAAMG,qBAAqB,CAAG5B,GAC3Bc,EAAE,GACFe,eAAe,CAAC,CAAEC,IAAK,EAAGC,OAAQ,GAAI,GACzC,MAAMC,MAAQV,SAASI,aAAa,CAAC,MACrCM,CAAAA,MAAML,EAAE,CAAG,OACXK,CAAAA,MAAMJ,qBAAqB,CAAG5B,GAC3Bc,EAAE,GACFe,eAAe,CAAC,CAAEC,IAAK,IAAKC,OAAQ,GAAI,GAC3CT,SAASC,IAAI,CAACU,WAAW,CAACR,OAC1BH,SAASC,IAAI,CAACU,WAAW,CAACD,OAE1B,MAAME,aAAe,CACnB,CAAEP,GAAI,QAASQ,UAAW,aAAc,EACxC,CAAER,GAAI,QAASQ,UAAW,YAAa,EACxC,CAED,KAAM,CAAEC,MAAM,CAAE,CAAGnC,WAAW,IAAME,sBAAsB+B,eAG1DhC,IAAI,KACFF,GAAGuC,YAAY,EACjB,GACA1C,OAAOuC,OAAOC,OAAO,EAAEC,IAAI,CAAC,eAG5BlC,iBACE,CACE,CACEsD,OAAQ1B,MACR2B,eAAgB,KAClB,EACD,CACD,CAAC,GAIH9D,OAAOuC,OAAOC,OAAO,EAAEC,IAAI,CAAC,cAC9B,GAEA1C,GAAG,iEAAkE,KACnE,MAAM6B,MAAQH,SAASI,aAAa,CAAC,MACrCD,CAAAA,MAAME,EAAE,CAAG,OACXF,CAAAA,MAAMG,qBAAqB,CAAG5B,GAC3Bc,EAAE,GACFe,eAAe,CAAC,CAAEC,IAAK,EAAGC,OAAQ,EAAG,GACxC,MAAMC,MAAQV,SAASI,aAAa,CAAC,MACrCM,CAAAA,MAAML,EAAE,CAAG,OAEXK,CAAAA,MAAMJ,qBAAqB,CAAG5B,GAC3Bc,EAAE,GACFe,eAAe,CAAC,CAAEC,IAAK,GAAIC,OAAQ,GAAI,GAC1C,MAAMqC,MAAQ9C,SAASI,aAAa,CAAC,MACrC0C,CAAAA,MAAMzC,EAAE,CAAG,OACXyC,CAAAA,MAAMxC,qBAAqB,CAAG5B,GAC3Bc,EAAE,GACFe,eAAe,CAAC,CAAEC,IAAK,GAAIC,OAAQ,GAAI,GAC1CT,SAASC,IAAI,CAACU,WAAW,CAACR,OAC1BH,SAASC,IAAI,CAACU,WAAW,CAACD,OAC1BV,SAASC,IAAI,CAACU,WAAW,CAACmC,OAE1B,MAAMlC,aAAe,CACnB,CAAEP,GAAI,QAASQ,UAAW,aAAc,EACxC,CAAER,GAAI,QAASQ,UAAW,YAAa,EACvC,CAAER,GAAI,QAASQ,UAAW,YAAa,EACxC,CAED,KAAM,CAAEC,MAAM,CAAE,CAAGnC,WAAW,IAAME,sBAAsB+B,eAM1DhC,IAAI,KACFF,GAAGuC,YAAY,EACjB,GACA1C,OAAOuC,OAAOC,OAAO,EAAEC,IAAI,CAAC,cAG5BpC,IAAI,KACFE,iBACE,CACE,CACEsD,OAAQjC,MACRkC,eAAgB,MAChBC,mBAAoB,CAClB9B,IAAK,EACLC,OAAQ,GACR8B,KAAM,EACNC,MAAO,EACPC,EAAG,EACHC,EAAG,EACHC,MAAO,EACPC,OAAQ,EACV,CACF,EACA,CACER,OAAQ1B,MACR2B,eAAgB,KAChBC,mBAAoB,CAClB9B,IAAK,IACLC,OAAQ,IACR8B,KAAM,EACNC,MAAO,EACPC,EAAG,EACHC,EAAG,IACHC,MAAO,EACPC,OAAQ,GACV,CACF,EACA,CACER,OAAQU,MACRT,eAAgB,KAChBC,mBAAoB,CAClB9B,IAAK,GACLC,OAAQ,IACR8B,KAAM,EACNC,MAAO,EACPC,EAAG,EACHC,EAAG,GACHC,MAAO,EACPC,OAAQ,EACV,CACF,EACD,CACD,CAAC,EAEL,GAKArE,OAAOuC,OAAOC,OAAO,EAAEC,IAAI,CAAC,aAC9B,GAEA1C,GAAG,kCAAmC,KACpC,MAAM4C,KAAOlB,SAASI,aAAa,CAAC,MACpCc,CAAAA,KAAKb,EAAE,CAAG,QACVL,SAASC,IAAI,CAACU,WAAW,CAACO,MAE1B,MAAMN,aAAe,CAAC,CAAEP,GAAI,QAASQ,UAAW,aAAc,EAAE,CAEhE,KAAM,CAAEkC,OAAO,CAAE,CAAGpE,WAAW,IAAME,sBAAsB+B,eAE3DmC,UAEAxE,OAAOU,gBAAgBqC,gBAAgB,EACzC,GAEAhD,GAAG,8CAA+C,KAChD,MAAM6B,MAAQH,SAASI,aAAa,CAAC,MACrCD,CAAAA,MAAME,EAAE,CAAG,QACX,MAAMK,MAAQV,SAASI,aAAa,CAAC,MACrCM,CAAAA,MAAML,EAAE,CAAG,QACXL,SAASC,IAAI,CAACU,WAAW,CAACR,OAC1BH,SAASC,IAAI,CAACU,WAAW,CAACD,OAE1B,KAAM,CAAES,QAAQ,CAAE,CAAGxC,WACnB,CAAC,CAAEiC,YAAY,CAAE,GAAK/B,sBAAsB+B,cAC5C,CACEQ,aAAc,CACZR,aAAc,CAAC,CAAEP,GAAI,QAASQ,UAAW,aAAc,EAAE,AAC3D,CACF,GAGFtC,OAAOe,sBAAsBuC,qBAAqB,CAAC,GACnDtD,OAAOQ,aAAa8C,qBAAqB,CAAC,GAG1CV,SAAS,CACPP,aAAc,CACZ,CAAEP,GAAI,QAASQ,UAAW,aAAc,EACxC,CAAER,GAAI,QAASQ,UAAW,YAAa,EACxC,AACH,GAGAtC,OAAOU,gBAAgB4C,qBAAqB,CAAC,GAC7CtD,OAAOe,sBAAsBuC,qBAAqB,CAAC,GACnDtD,OAAOQ,aAAa8C,qBAAqB,CAAC,EAC5C,GAEAvD,GAAG,+CAAgD,KACjD,MAAM4C,KAAOlB,SAASI,aAAa,CAAC,MACpCc,CAAAA,KAAKb,EAAE,CAAG,QACVL,SAASC,IAAI,CAACU,WAAW,CAACO,MAE1B,MAAMN,aAAe,CACnB,CAAEP,GAAI,QAASQ,UAAW,aAAc,EACxC,CAAER,GAAI,QAASQ,UAAW,YAAa,EACxC,CAEDlC,WAAW,IAAME,sBAAsB+B,eAEvC,MAAMoC,OAAStE,GAAGqD,KAAK,CAAC1C,OAAQ,yBAGhCP,iBACE,CACE,CACEsD,OAAQlB,KACRmB,eAAgB,IAClB,EACD,CACD,CAAC,GAGH9D,OAAOyE,QAAQ1B,gBAAgB,GAE/B0B,OAAOb,WAAW,EACpB,EACF"}
|
|
1
|
+
{"version":3,"sources":["../../../src/core/hooks/use-themed-scrollpoints.test.ts"],"sourcesContent":["/**\n * @vitest-environment jsdom\n */\n\nimport { describe, it, expect, beforeEach, afterEach, vi } from \"vitest\";\nimport { renderHook, act } from \"@testing-library/react\";\nimport { useThemedScrollpoints } from \"./use-themed-scrollpoints\";\n\ndescribe(\"useThemedScrollpoints\", () => {\n let observerCallback: IntersectionObserverCallback;\n let mockObserve: ReturnType<typeof vi.fn>;\n let mockUnobserve: ReturnType<typeof vi.fn>;\n let mockDisconnect: ReturnType<typeof vi.fn>;\n let originalIntersectionObserver: typeof IntersectionObserver;\n let originalRequestAnimationFrame: typeof requestAnimationFrame;\n\n beforeEach(() => {\n vi.useFakeTimers();\n\n // Save originals to prevent test pollution\n originalIntersectionObserver = global.IntersectionObserver;\n originalRequestAnimationFrame = global.requestAnimationFrame;\n\n // Mock IntersectionObserver\n mockObserve = vi.fn();\n mockUnobserve = vi.fn();\n mockDisconnect = vi.fn();\n\n // Vitest 4: constructor mocks must use `function`/`class`, not arrows —\n // arrows have no [[Construct]] and `new` against them throws.\n global.IntersectionObserver = vi.fn(function (\n callback: IntersectionObserverCallback,\n ) {\n observerCallback = callback;\n return {\n observe: mockObserve,\n unobserve: mockUnobserve,\n disconnect: mockDisconnect,\n };\n }) as unknown as typeof IntersectionObserver;\n\n // Mock requestAnimationFrame\n global.requestAnimationFrame = vi.fn((cb) => {\n cb(0);\n return 0;\n });\n });\n\n afterEach(() => {\n vi.clearAllMocks();\n vi.useRealTimers();\n document.body.innerHTML = \"\";\n // Restore originals to prevent test pollution\n global.IntersectionObserver = originalIntersectionObserver;\n global.requestAnimationFrame = originalRequestAnimationFrame;\n });\n\n it(\"returns first scrollpoint className on mount\", () => {\n // Create DOM elements\n const elem1 = document.createElement(\"div\");\n elem1.id = \"zone1\";\n elem1.getBoundingClientRect = vi\n .fn()\n .mockReturnValue({ top: 0, bottom: 200 });\n const elem2 = document.createElement(\"div\");\n elem2.id = \"zone2\";\n elem2.getBoundingClientRect = vi\n .fn()\n .mockReturnValue({ top: 200, bottom: 400 });\n document.body.appendChild(elem1);\n document.body.appendChild(elem2);\n\n const scrollpoints = [\n { id: \"zone1\", className: \"theme-light\" },\n { id: \"zone2\", className: \"theme-dark\" },\n ];\n\n const { result } = renderHook(() => useThemedScrollpoints(scrollpoints));\n\n // Initial state is empty\n expect(result.current).toBe(\"\");\n\n // Advance timer to trigger initial check\n act(() => {\n vi.runAllTimers();\n });\n\n // Should now have the first scrollpoint's className\n expect(result.current).toBe(\"theme-light\");\n });\n\n it(\"returns empty string when no scrollpoints provided\", () => {\n const { result } = renderHook(() => useThemedScrollpoints([]));\n expect(result.current).toBe(\"\");\n });\n\n it(\"clears activeClassName when scrollpoints changes from non-empty to empty\", () => {\n const elem = document.createElement(\"div\");\n elem.id = \"zone1\";\n elem.getBoundingClientRect = vi\n .fn()\n .mockReturnValue({ top: 0, bottom: 200 });\n document.body.appendChild(elem);\n\n const { result, rerender } = renderHook(\n ({ scrollpoints }) => useThemedScrollpoints(scrollpoints),\n {\n initialProps: {\n scrollpoints: [{ id: \"zone1\", className: \"theme-light\" }],\n },\n },\n );\n\n // Wait for initial check\n act(() => {\n vi.runAllTimers();\n });\n expect(result.current).toBe(\"theme-light\");\n\n // Change to empty scrollpoints\n rerender({ scrollpoints: [] });\n\n // Should clear the className\n expect(result.current).toBe(\"\");\n });\n\n it(\"does not create IntersectionObserver when no scrollpoints provided\", () => {\n renderHook(() => useThemedScrollpoints([]));\n\n expect(IntersectionObserver).not.toHaveBeenCalled();\n });\n\n it(\"creates IntersectionObserver with correct config\", () => {\n const scrollpoints = [{ id: \"zone1\", className: \"theme-light\" }];\n\n // Create DOM element\n const elem = document.createElement(\"div\");\n elem.id = \"zone1\";\n document.body.appendChild(elem);\n\n renderHook(() => useThemedScrollpoints(scrollpoints));\n\n expect(IntersectionObserver).toHaveBeenCalledWith(\n expect.any(Function),\n expect.objectContaining({\n rootMargin: \"-64px 0px 0px 0px\",\n threshold: 0,\n }),\n );\n });\n\n it(\"observes all scrollpoint elements that exist in DOM\", () => {\n // Create DOM elements\n const elem1 = document.createElement(\"div\");\n elem1.id = \"zone1\";\n const elem2 = document.createElement(\"div\");\n elem2.id = \"zone2\";\n document.body.appendChild(elem1);\n document.body.appendChild(elem2);\n\n const scrollpoints = [\n { id: \"zone1\", className: \"theme-light\" },\n { id: \"zone2\", className: \"theme-dark\" },\n ];\n\n renderHook(() => useThemedScrollpoints(scrollpoints));\n\n expect(mockObserve).toHaveBeenCalledTimes(2);\n expect(mockObserve).toHaveBeenCalledWith(elem1);\n expect(mockObserve).toHaveBeenCalledWith(elem2);\n });\n\n it(\"logs warning for missing DOM elements\", () => {\n const consoleWarn = vi.spyOn(console, \"warn\").mockImplementation(() => {});\n\n const scrollpoints = [{ id: \"non-existent\", className: \"theme\" }];\n\n renderHook(() => useThemedScrollpoints(scrollpoints));\n\n expect(consoleWarn).toHaveBeenCalledWith(\n expect.stringContaining('Element with id \"non-existent\" not found'),\n );\n\n consoleWarn.mockRestore();\n });\n\n it(\"observes existing elements and warns about missing ones\", () => {\n const consoleWarn = vi.spyOn(console, \"warn\").mockImplementation(() => {});\n\n // Create only one element\n const elem = document.createElement(\"div\");\n elem.id = \"zone1\";\n document.body.appendChild(elem);\n\n const scrollpoints = [\n { id: \"zone1\", className: \"theme-light\" },\n { id: \"missing\", className: \"theme-dark\" },\n ];\n\n renderHook(() => useThemedScrollpoints(scrollpoints));\n\n expect(mockObserve).toHaveBeenCalledTimes(1);\n expect(mockObserve).toHaveBeenCalledWith(elem);\n expect(consoleWarn).toHaveBeenCalledWith(\n expect.stringContaining('Element with id \"missing\" not found'),\n );\n\n consoleWarn.mockRestore();\n });\n\n it(\"updates className when intersection occurs\", () => {\n // Create DOM elements\n const elem1 = document.createElement(\"div\");\n elem1.id = \"zone1\";\n elem1.getBoundingClientRect = vi\n .fn()\n .mockReturnValue({ top: 0, bottom: 200 });\n const elem2 = document.createElement(\"div\");\n elem2.id = \"zone2\";\n elem2.getBoundingClientRect = vi\n .fn()\n .mockReturnValue({ top: 50, bottom: 250 });\n document.body.appendChild(elem1);\n document.body.appendChild(elem2);\n\n const scrollpoints = [\n { id: \"zone1\", className: \"theme-light\" },\n { id: \"zone2\", className: \"theme-dark\" },\n ];\n\n const { result } = renderHook(() => useThemedScrollpoints(scrollpoints));\n\n // Wait for initial check\n // elem2 (top=50) is closer to header (64) than elem1 (top=0)\n // distance for elem1: |0 - 64| = 64\n // distance for elem2: |50 - 64| = 14\n act(() => {\n vi.runAllTimers();\n });\n expect(result.current).toBe(\"theme-dark\");\n\n // Simulate scrolling - elem1 moves closer to header position\n act(() => {\n observerCallback(\n [\n {\n target: elem1,\n isIntersecting: true,\n boundingClientRect: {\n top: 60,\n bottom: 260,\n left: 0,\n right: 0,\n x: 0,\n y: 60,\n width: 0,\n height: 200,\n },\n } as unknown as IntersectionObserverEntry,\n {\n target: elem2,\n isIntersecting: true,\n boundingClientRect: {\n top: 100,\n bottom: 300,\n left: 0,\n right: 0,\n x: 0,\n y: 100,\n width: 0,\n height: 200,\n },\n } as unknown as IntersectionObserverEntry,\n ],\n {} as IntersectionObserver,\n );\n });\n\n // elem1 is now closer to header (distance=4 vs elem2 distance=36)\n expect(result.current).toBe(\"theme-light\");\n });\n\n it(\"does not update className if same scrollpoint intersects again\", () => {\n const elem = document.createElement(\"div\");\n elem.id = \"zone1\";\n elem.getBoundingClientRect = vi\n .fn()\n .mockReturnValue({ top: 0, bottom: 200 });\n document.body.appendChild(elem);\n\n const scrollpoints = [{ id: \"zone1\", className: \"theme-light\" }];\n\n const { result } = renderHook(() => useThemedScrollpoints(scrollpoints));\n\n // Wait for initial check\n act(() => {\n vi.runAllTimers();\n });\n expect(result.current).toBe(\"theme-light\");\n\n // Simulate intersection with same element\n const renderCount = result.current;\n observerCallback(\n [\n {\n target: elem,\n isIntersecting: true,\n boundingClientRect: {\n top: 0,\n bottom: 200,\n left: 0,\n right: 0,\n x: 0,\n y: 0,\n width: 0,\n height: 200,\n },\n } as unknown as IntersectionObserverEntry,\n ],\n {} as IntersectionObserver,\n );\n\n // Should not trigger re-render (className unchanged)\n expect(result.current).toBe(renderCount);\n });\n\n it(\"ignores non-intersecting entries\", () => {\n const elem1 = document.createElement(\"div\");\n elem1.id = \"zone1\";\n elem1.getBoundingClientRect = vi\n .fn()\n .mockReturnValue({ top: 0, bottom: 200 });\n const elem2 = document.createElement(\"div\");\n elem2.id = \"zone2\";\n elem2.getBoundingClientRect = vi\n .fn()\n .mockReturnValue({ top: 200, bottom: 400 });\n document.body.appendChild(elem1);\n document.body.appendChild(elem2);\n\n const scrollpoints = [\n { id: \"zone1\", className: \"theme-light\" },\n { id: \"zone2\", className: \"theme-dark\" },\n ];\n\n const { result } = renderHook(() => useThemedScrollpoints(scrollpoints));\n\n // Wait for initial check\n act(() => {\n vi.runAllTimers();\n });\n expect(result.current).toBe(\"theme-light\");\n\n // Simulate non-intersecting entry\n observerCallback(\n [\n {\n target: elem2,\n isIntersecting: false,\n } as unknown as IntersectionObserverEntry,\n ],\n {} as IntersectionObserver,\n );\n\n // Should remain unchanged\n expect(result.current).toBe(\"theme-light\");\n });\n\n it(\"uses closest element to header when multiple entries intersect\", () => {\n const elem1 = document.createElement(\"div\");\n elem1.id = \"zone1\";\n elem1.getBoundingClientRect = vi\n .fn()\n .mockReturnValue({ top: 0, bottom: 50 });\n const elem2 = document.createElement(\"div\");\n elem2.id = \"zone2\";\n // zone2 top is at 60, which is closer to HEADER_HEIGHT (64) than zone3\n elem2.getBoundingClientRect = vi\n .fn()\n .mockReturnValue({ top: 60, bottom: 200 });\n const elem3 = document.createElement(\"div\");\n elem3.id = \"zone3\";\n elem3.getBoundingClientRect = vi\n .fn()\n .mockReturnValue({ top: 10, bottom: 100 });\n document.body.appendChild(elem1);\n document.body.appendChild(elem2);\n document.body.appendChild(elem3);\n\n const scrollpoints = [\n { id: \"zone1\", className: \"theme-light\" },\n { id: \"zone2\", className: \"theme-dark\" },\n { id: \"zone3\", className: \"theme-blue\" },\n ];\n\n const { result } = renderHook(() => useThemedScrollpoints(scrollpoints));\n\n // Wait for initial check\n // elem2 (top=60) is closest to header (64) with distance=4\n // elem3 (top=10) has distance=54\n // elem1 (top=0) doesn't contain header (bottom=50 < 64)\n act(() => {\n vi.runAllTimers();\n });\n expect(result.current).toBe(\"theme-dark\");\n\n // Simulate scrolling where elem3 becomes closest to header\n act(() => {\n observerCallback(\n [\n {\n target: elem1,\n isIntersecting: false,\n boundingClientRect: {\n top: 0,\n bottom: 50,\n left: 0,\n right: 0,\n x: 0,\n y: 0,\n width: 0,\n height: 50,\n },\n } as unknown as IntersectionObserverEntry,\n {\n target: elem2,\n isIntersecting: true,\n boundingClientRect: {\n top: 100,\n bottom: 300,\n left: 0,\n right: 0,\n x: 0,\n y: 100,\n width: 0,\n height: 200,\n },\n } as unknown as IntersectionObserverEntry,\n {\n target: elem3,\n isIntersecting: true,\n boundingClientRect: {\n top: 62,\n bottom: 152,\n left: 0,\n right: 0,\n x: 0,\n y: 62,\n width: 0,\n height: 90,\n },\n } as unknown as IntersectionObserverEntry,\n ],\n {} as IntersectionObserver,\n );\n });\n\n // elem3 is now closest to HEADER_HEIGHT (64)\n // distance for elem2: |100 - 64| = 36\n // distance for elem3: |62 - 64| = 2\n expect(result.current).toBe(\"theme-blue\");\n });\n\n it(\"disconnects observer on unmount\", () => {\n const elem = document.createElement(\"div\");\n elem.id = \"zone1\";\n document.body.appendChild(elem);\n\n const scrollpoints = [{ id: \"zone1\", className: \"theme-light\" }];\n\n const { unmount } = renderHook(() => useThemedScrollpoints(scrollpoints));\n\n unmount();\n\n expect(mockDisconnect).toHaveBeenCalled();\n });\n\n it(\"recreates observer when scrollpoints change\", () => {\n const elem1 = document.createElement(\"div\");\n elem1.id = \"zone1\";\n const elem2 = document.createElement(\"div\");\n elem2.id = \"zone2\";\n document.body.appendChild(elem1);\n document.body.appendChild(elem2);\n\n const { rerender } = renderHook(\n ({ scrollpoints }) => useThemedScrollpoints(scrollpoints),\n {\n initialProps: {\n scrollpoints: [{ id: \"zone1\", className: \"theme-light\" }],\n },\n },\n );\n\n expect(IntersectionObserver).toHaveBeenCalledTimes(1);\n expect(mockObserve).toHaveBeenCalledTimes(1);\n\n // Change scrollpoints\n rerender({\n scrollpoints: [\n { id: \"zone1\", className: \"theme-light\" },\n { id: \"zone2\", className: \"theme-dark\" },\n ],\n });\n\n // Should disconnect old observer and create new one\n expect(mockDisconnect).toHaveBeenCalledTimes(1);\n expect(IntersectionObserver).toHaveBeenCalledTimes(2);\n expect(mockObserve).toHaveBeenCalledTimes(3); // 1 from first render + 2 from second\n });\n\n it(\"uses requestAnimationFrame for state updates\", () => {\n const elem = document.createElement(\"div\");\n elem.id = \"zone1\";\n document.body.appendChild(elem);\n\n const scrollpoints = [\n { id: \"zone1\", className: \"theme-light\" },\n { id: \"zone2\", className: \"theme-dark\" },\n ];\n\n renderHook(() => useThemedScrollpoints(scrollpoints));\n\n const rafSpy = vi.spyOn(global, \"requestAnimationFrame\");\n\n // Simulate intersection\n observerCallback(\n [\n {\n target: elem,\n isIntersecting: true,\n } as unknown as IntersectionObserverEntry,\n ],\n {} as IntersectionObserver,\n );\n\n expect(rafSpy).toHaveBeenCalled();\n\n rafSpy.mockRestore();\n });\n});\n"],"names":["describe","it","expect","beforeEach","afterEach","vi","renderHook","act","useThemedScrollpoints","observerCallback","mockObserve","mockUnobserve","mockDisconnect","originalIntersectionObserver","originalRequestAnimationFrame","useFakeTimers","global","IntersectionObserver","requestAnimationFrame","fn","callback","observe","unobserve","disconnect","cb","clearAllMocks","useRealTimers","document","body","innerHTML","elem1","createElement","id","getBoundingClientRect","mockReturnValue","top","bottom","elem2","appendChild","scrollpoints","className","result","current","toBe","runAllTimers","elem","rerender","initialProps","not","toHaveBeenCalled","toHaveBeenCalledWith","any","Function","objectContaining","rootMargin","threshold","toHaveBeenCalledTimes","consoleWarn","spyOn","console","mockImplementation","stringContaining","mockRestore","target","isIntersecting","boundingClientRect","left","right","x","y","width","height","renderCount","elem3","unmount","rafSpy"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
function _define_property(obj,key,value){if(key in obj){Object.defineProperty(obj,key,{value:value,enumerable:true,configurable:true,writable:true})}else{obj[key]=value}return obj}import{InsightsService}from"./service";import*as logger from"./logger";export class InsightsCommandQueue{executeInitInsights(config){this.debugMode=!!config.debug;if(this.debugMode){logger.debug("Initializing insights")}this.realImplementation=new InsightsService;this.realImplementation.initInsights(config);this.isInitialized=true;this.processQueue()}processQueue(){if(this.debugMode){logger.debug(`Processing ${this.queue.length} queued commands`)}while(this.queue.length>0){const cmd=this.queue.shift();if(cmd&&this.realImplementation&&typeof this.realImplementation[cmd.methodName]==="function"){try{if(this.debugMode){logger.debug(`Executing queued command: ${cmd.methodName}`,cmd.args)}const fn=this.realImplementation[cmd.methodName];fn.apply(this.realImplementation,cmd.args)}catch(e){if(this.debugMode){logger.error(`Error executing queued command: ${cmd.methodName}`,e)}}}}}initInsights(_config){}enableDebugMode(){}disableDebugMode(){}identify(_identity){}trackPageView(_options){}track(_event,_properties){}
|
|
1
|
+
function _define_property(obj,key,value){if(key in obj){Object.defineProperty(obj,key,{value:value,enumerable:true,configurable:true,writable:true})}else{obj[key]=value}return obj}import{InsightsService}from"./service";import*as logger from"./logger";export class InsightsCommandQueue{executeInitInsights(config){this.debugMode=!!config.debug;if(this.debugMode){logger.debug("Initializing insights")}this.realImplementation=new InsightsService;this.realImplementation.initInsights(config);this.isInitialized=true;this.processQueue()}processQueue(){if(this.debugMode){logger.debug(`Processing ${this.queue.length} queued commands`)}while(this.queue.length>0){const cmd=this.queue.shift();if(cmd&&this.realImplementation&&typeof this.realImplementation[cmd.methodName]==="function"){try{if(this.debugMode){logger.debug(`Executing queued command: ${cmd.methodName}`,cmd.args)}const fn=this.realImplementation[cmd.methodName];fn.apply(this.realImplementation,cmd.args)}catch(e){if(this.debugMode){logger.error(`Error executing queued command: ${cmd.methodName}`,e)}}}}}initInsights(_config){}enableDebugMode(){}disableDebugMode(){}identify(_identity){}trackPageView(_options){}track(_event,_properties){}setupObserver(){return()=>{}}constructor(){_define_property(this,"isInitialized",false);_define_property(this,"queue",[]);_define_property(this,"debugMode",false);_define_property(this,"realImplementation",null);return new Proxy(this,{get:(target,prop)=>{if(prop in target&&typeof target[prop]!=="function"){return target[prop]}return(...args)=>{if(!target.isInitialized||!target.realImplementation){if(prop==="initInsights"){target.executeInitInsights(args[0]);return}if(target.debugMode){logger.debug(`Queuing method call: ${String(prop)}`,args)}target.queue.push({methodName:prop,args});return}if(typeof target.realImplementation[prop]==="function"){return target.realImplementation[prop](...args)}}}})}}
|
|
2
2
|
//# sourceMappingURL=command-queue.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/core/insights/command-queue.ts"],"sourcesContent":["import {\n AnalyticsService,\n Command,\n InsightsConfig,\n InsightsIdentity,\n TrackPageViewOptions,\n} from \"./types\";\nimport { InsightsService } from \"./service\";\nimport * as logger from \"./logger\";\n\n// Queue handler that will collect commands before initialization\nexport class InsightsCommandQueue implements AnalyticsService {\n private isInitialized: boolean = false;\n private queue: Command[] = [];\n private debugMode: boolean = false;\n private realImplementation: InsightsService | null = null;\n\n constructor() {\n // Create a proxy that will either queue commands or execute them directly\n return new Proxy(this, {\n get: (target: InsightsCommandQueue, prop: keyof InsightsCommandQueue) => {\n // Return actual properties of the queue\n if (prop in target && typeof target[prop] !== \"function\") {\n return target[prop];\n }\n\n // Return a function that either queues or executes the method call\n return (...args: unknown[]) => {\n if (!target.isInitialized || !target.realImplementation) {\n // Queue the command for later execution\n if (prop === \"initInsights\") {\n // Special handling for initInsights - execute it right away\n target.executeInitInsights(args[0] as InsightsConfig);\n return;\n }\n\n // For debug logging\n if (target.debugMode) {\n logger.debug(`Queuing method call: ${String(prop)}`, args);\n }\n\n target.queue.push({\n methodName: prop,\n args,\n });\n\n return;\n }\n\n // Execute the command immediately on the real implementation\n if (typeof target.realImplementation[prop] === \"function\") {\n return (\n target.realImplementation[prop] as (...args: unknown[]) => unknown\n )(...args);\n }\n };\n },\n });\n }\n\n // Special handling for init since it needs to happen right away\n private executeInitInsights(config: InsightsConfig): void {\n this.debugMode = !!config.debug;\n\n if (this.debugMode) {\n logger.debug(\"Initializing insights\");\n }\n\n // Create and initialize the real implementation\n this.realImplementation = new InsightsService();\n this.realImplementation.initInsights(config);\n\n // Mark as initialized and process the queue\n this.isInitialized = true;\n this.processQueue();\n }\n\n // Process all queued commands\n private processQueue(): void {\n if (this.debugMode) {\n logger.debug(`Processing ${this.queue.length} queued commands`);\n }\n\n while (this.queue.length > 0) {\n const cmd = this.queue.shift();\n if (\n cmd &&\n this.realImplementation &&\n typeof this.realImplementation[cmd.methodName] === \"function\"\n ) {\n try {\n if (this.debugMode) {\n logger.debug(\n `Executing queued command: ${cmd.methodName}`,\n cmd.args,\n );\n }\n\n const fn = this.realImplementation[cmd.methodName] as (\n ...args: unknown[]\n ) => unknown;\n\n // Execute the command with the real implementation\n fn.apply(this.realImplementation, cmd.args as unknown[]);\n } catch (e) {\n if (this.debugMode) {\n logger.error(\n `Error executing queued command: ${cmd.methodName}`,\n e,\n );\n }\n }\n }\n }\n }\n\n // Implement all methods required by AnalyticsService to satisfy TypeScript\n // (These won't be called directly due to the proxy)\n initInsights(_config: InsightsConfig): void {}\n enableDebugMode(): void {}\n disableDebugMode(): void {}\n identify(_identity: InsightsIdentity): void {}\n trackPageView(_options?: TrackPageViewOptions): void {}\n track(_event: string, _properties?: Record<string, unknown>): void {}\n
|
|
1
|
+
{"version":3,"sources":["../../../src/core/insights/command-queue.ts"],"sourcesContent":["import {\n AnalyticsService,\n Command,\n InsightsConfig,\n InsightsIdentity,\n TrackPageViewOptions,\n} from \"./types\";\nimport { InsightsService } from \"./service\";\nimport * as logger from \"./logger\";\n\n// Queue handler that will collect commands before initialization\nexport class InsightsCommandQueue implements AnalyticsService {\n private isInitialized: boolean = false;\n private queue: Command[] = [];\n private debugMode: boolean = false;\n private realImplementation: InsightsService | null = null;\n\n constructor() {\n // Create a proxy that will either queue commands or execute them directly\n return new Proxy(this, {\n get: (target: InsightsCommandQueue, prop: keyof InsightsCommandQueue) => {\n // Return actual properties of the queue\n if (prop in target && typeof target[prop] !== \"function\") {\n return target[prop];\n }\n\n // Return a function that either queues or executes the method call\n return (...args: unknown[]) => {\n if (!target.isInitialized || !target.realImplementation) {\n // Queue the command for later execution\n if (prop === \"initInsights\") {\n // Special handling for initInsights - execute it right away\n target.executeInitInsights(args[0] as InsightsConfig);\n return;\n }\n\n // For debug logging\n if (target.debugMode) {\n logger.debug(`Queuing method call: ${String(prop)}`, args);\n }\n\n target.queue.push({\n methodName: prop,\n args,\n });\n\n return;\n }\n\n // Execute the command immediately on the real implementation\n if (typeof target.realImplementation[prop] === \"function\") {\n return (\n target.realImplementation[prop] as (...args: unknown[]) => unknown\n )(...args);\n }\n };\n },\n });\n }\n\n // Special handling for init since it needs to happen right away\n private executeInitInsights(config: InsightsConfig): void {\n this.debugMode = !!config.debug;\n\n if (this.debugMode) {\n logger.debug(\"Initializing insights\");\n }\n\n // Create and initialize the real implementation\n this.realImplementation = new InsightsService();\n this.realImplementation.initInsights(config);\n\n // Mark as initialized and process the queue\n this.isInitialized = true;\n this.processQueue();\n }\n\n // Process all queued commands\n private processQueue(): void {\n if (this.debugMode) {\n logger.debug(`Processing ${this.queue.length} queued commands`);\n }\n\n while (this.queue.length > 0) {\n const cmd = this.queue.shift();\n if (\n cmd &&\n this.realImplementation &&\n typeof this.realImplementation[cmd.methodName] === \"function\"\n ) {\n try {\n if (this.debugMode) {\n logger.debug(\n `Executing queued command: ${cmd.methodName}`,\n cmd.args,\n );\n }\n\n const fn = this.realImplementation[cmd.methodName] as (\n ...args: unknown[]\n ) => unknown;\n\n // Execute the command with the real implementation\n fn.apply(this.realImplementation, cmd.args as unknown[]);\n } catch (e) {\n if (this.debugMode) {\n logger.error(\n `Error executing queued command: ${cmd.methodName}`,\n e,\n );\n }\n }\n }\n }\n }\n\n // Implement all methods required by AnalyticsService to satisfy TypeScript\n // (These won't be called directly due to the proxy)\n initInsights(_config: InsightsConfig): void {}\n enableDebugMode(): void {}\n disableDebugMode(): void {}\n identify(_identity: InsightsIdentity): void {}\n trackPageView(_options?: TrackPageViewOptions): void {}\n track(_event: string, _properties?: Record<string, unknown>): void {}\n setupObserver(): () => void {\n return () => {};\n }\n}\n"],"names":["InsightsService","logger","InsightsCommandQueue","executeInitInsights","config","debugMode","debug","realImplementation","initInsights","isInitialized","processQueue","queue","length","cmd","shift","methodName","args","fn","apply","e","error","_config","enableDebugMode","disableDebugMode","identify","_identity","trackPageView","_options","track","_event","_properties","setupObserver","Proxy","get","target","prop","String","push"],"mappings":"oLAOA,OAASA,eAAe,KAAQ,WAAY,AAC5C,WAAYC,WAAY,UAAW,AAGnC,QAAO,MAAMC,qBAkDX,AAAQC,oBAAoBC,MAAsB,CAAQ,CACxD,IAAI,CAACC,SAAS,CAAG,CAAC,CAACD,OAAOE,KAAK,CAE/B,GAAI,IAAI,CAACD,SAAS,CAAE,CAClBJ,OAAOK,KAAK,CAAC,wBACf,CAGA,IAAI,CAACC,kBAAkB,CAAG,IAAIP,gBAC9B,IAAI,CAACO,kBAAkB,CAACC,YAAY,CAACJ,OAGrC,CAAA,IAAI,CAACK,aAAa,CAAG,KACrB,IAAI,CAACC,YAAY,EACnB,CAGA,AAAQA,cAAqB,CAC3B,GAAI,IAAI,CAACL,SAAS,CAAE,CAClBJ,OAAOK,KAAK,CAAC,CAAC,WAAW,EAAE,IAAI,CAACK,KAAK,CAACC,MAAM,CAAC,gBAAgB,CAAC,CAChE,CAEA,MAAO,IAAI,CAACD,KAAK,CAACC,MAAM,CAAG,EAAG,CAC5B,MAAMC,IAAM,IAAI,CAACF,KAAK,CAACG,KAAK,GAC5B,GACED,KACA,IAAI,CAACN,kBAAkB,EACvB,OAAO,IAAI,CAACA,kBAAkB,CAACM,IAAIE,UAAU,CAAC,GAAK,WACnD,CACA,GAAI,CACF,GAAI,IAAI,CAACV,SAAS,CAAE,CAClBJ,OAAOK,KAAK,CACV,CAAC,0BAA0B,EAAEO,IAAIE,UAAU,CAAC,CAAC,CAC7CF,IAAIG,IAAI,CAEZ,CAEA,MAAMC,GAAK,IAAI,CAACV,kBAAkB,CAACM,IAAIE,UAAU,CAAC,CAKlDE,GAAGC,KAAK,CAAC,IAAI,CAACX,kBAAkB,CAAEM,IAAIG,IAAI,CAC5C,CAAE,MAAOG,EAAG,CACV,GAAI,IAAI,CAACd,SAAS,CAAE,CAClBJ,OAAOmB,KAAK,CACV,CAAC,gCAAgC,EAAEP,IAAIE,UAAU,CAAC,CAAC,CACnDI,EAEJ,CACF,CACF,CACF,CACF,CAIAX,aAAaa,OAAuB,CAAQ,CAAC,CAC7CC,iBAAwB,CAAC,CACzBC,kBAAyB,CAAC,CAC1BC,SAASC,SAA2B,CAAQ,CAAC,CAC7CC,cAAcC,QAA+B,CAAQ,CAAC,CACtDC,MAAMC,MAAc,CAAEC,WAAqC,CAAQ,CAAC,CACpEC,eAA4B,CAC1B,MAAO,KAAO,CAChB,CA7GA,aAAc,CALd,sBAAQtB,gBAAyB,OACjC,sBAAQE,QAAmB,EAAE,EAC7B,sBAAQN,YAAqB,OAC7B,sBAAQE,qBAA6C,MAInD,OAAO,IAAIyB,MAAM,IAAI,CAAE,CACrBC,IAAK,CAACC,OAA8BC,QAElC,GAAIA,QAAQD,QAAU,OAAOA,MAAM,CAACC,KAAK,GAAK,WAAY,CACxD,OAAOD,MAAM,CAACC,KAAK,AACrB,CAGA,MAAO,CAAC,GAAGnB,QACT,GAAI,CAACkB,OAAOzB,aAAa,EAAI,CAACyB,OAAO3B,kBAAkB,CAAE,CAEvD,GAAI4B,OAAS,eAAgB,CAE3BD,OAAO/B,mBAAmB,CAACa,IAAI,CAAC,EAAE,EAClC,MACF,CAGA,GAAIkB,OAAO7B,SAAS,CAAE,CACpBJ,OAAOK,KAAK,CAAC,CAAC,qBAAqB,EAAE8B,OAAOD,MAAM,CAAC,CAAEnB,KACvD,CAEAkB,OAAOvB,KAAK,CAAC0B,IAAI,CAAC,CAChBtB,WAAYoB,KACZnB,IACF,GAEA,MACF,CAGA,GAAI,OAAOkB,OAAO3B,kBAAkB,CAAC4B,KAAK,GAAK,WAAY,CACzD,OAAO,AACLD,OAAO3B,kBAAkB,CAAC4B,KAAK,IAC5BnB,KACP,CACF,CACF,CACF,EACF,CAqEF"}
|
package/core/insights/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{InsightsCommandQueue}from"./command-queue";const insights=new InsightsCommandQueue;export const initInsights=config=>insights.initInsights(config);export const enableDebugMode=()=>insights.enableDebugMode();export const disableDebugMode=()=>insights.disableDebugMode();export const identify=identity=>insights.identify(identity);export const trackPageView=options=>insights.trackPageView(options);export const track=(event,properties)=>insights.track(event,properties);export const
|
|
1
|
+
import{InsightsCommandQueue}from"./command-queue";const insights=new InsightsCommandQueue;export const initInsights=config=>insights.initInsights(config);export const enableDebugMode=()=>insights.enableDebugMode();export const disableDebugMode=()=>insights.disableDebugMode();export const identify=identity=>insights.identify(identity);export const trackPageView=options=>insights.trackPageView(options);export const track=(event,properties)=>insights.track(event,properties);export const setupObserver=()=>insights.setupObserver();
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/core/insights/index.ts"],"sourcesContent":["import {\n InsightsConfig,\n InsightsIdentity,\n TrackPageViewOptions,\n} from \"./types\";\nimport { InsightsCommandQueue } from \"./command-queue\";\nexport type { InsightsConfig };\n\n// Hi and welcome 👋\n//\n// The insights code is written using a Command Queue, or Deferred Execution pattern.\n// This pattern is useful when you need to queue up actions that should wait until\n// some initialization is complete. In this case, we want to queue up all the analytics\n// commands until the analytics service is initialized. This way, we can ensure that\n// no analytics events are lost during the initialization process. It looks wildly\n// different than other parts of Ably UI, but if you squint you realise it looks very\n// much like the services it's wrapping.\n//\n// There are three pieces working together here:\n// - The `AnalyticsService` interface, which defines the public methods that the insights\n// service will expose.\n// - The `InsightsCommandQueue` class, which is the main entry point for the insights\n// service. It acts as a proxy that will either queue up commands or execute\n// them directly on the real implementation.\n// - The `InsightsService` class, which is the real implementation that will be used\n// after initialization. It's responsible for initializing the underlying analytics\n// services (Mixpanel, Posthog & the data layer) and executing the queued commands.\n\n// Create the singleton instance with the command queue pattern\nconst insights = new InsightsCommandQueue();\n\n// Export the methods with the same interface as before\nexport const initInsights = (config: InsightsConfig) =>\n insights.initInsights(config);\nexport const enableDebugMode = () => insights.enableDebugMode();\nexport const disableDebugMode = () => insights.disableDebugMode();\nexport const identify = (identity: InsightsIdentity) =>\n insights.identify(identity);\nexport const trackPageView = (options?: TrackPageViewOptions) =>\n insights.trackPageView(options);\nexport const track = (event: string, properties?: Record<string, unknown>) =>\n insights.track(event, properties);\nexport const
|
|
1
|
+
{"version":3,"sources":["../../../src/core/insights/index.ts"],"sourcesContent":["import {\n InsightsConfig,\n InsightsIdentity,\n TrackPageViewOptions,\n} from \"./types\";\nimport { InsightsCommandQueue } from \"./command-queue\";\nexport type { InsightsConfig };\n\n// Hi and welcome 👋\n//\n// The insights code is written using a Command Queue, or Deferred Execution pattern.\n// This pattern is useful when you need to queue up actions that should wait until\n// some initialization is complete. In this case, we want to queue up all the analytics\n// commands until the analytics service is initialized. This way, we can ensure that\n// no analytics events are lost during the initialization process. It looks wildly\n// different than other parts of Ably UI, but if you squint you realise it looks very\n// much like the services it's wrapping.\n//\n// There are three pieces working together here:\n// - The `AnalyticsService` interface, which defines the public methods that the insights\n// service will expose.\n// - The `InsightsCommandQueue` class, which is the main entry point for the insights\n// service. It acts as a proxy that will either queue up commands or execute\n// them directly on the real implementation.\n// - The `InsightsService` class, which is the real implementation that will be used\n// after initialization. It's responsible for initializing the underlying analytics\n// services (Mixpanel, Posthog & the data layer) and executing the queued commands.\n\n// Create the singleton instance with the command queue pattern\nconst insights = new InsightsCommandQueue();\n\n// Export the methods with the same interface as before\nexport const initInsights = (config: InsightsConfig) =>\n insights.initInsights(config);\nexport const enableDebugMode = () => insights.enableDebugMode();\nexport const disableDebugMode = () => insights.disableDebugMode();\nexport const identify = (identity: InsightsIdentity) =>\n insights.identify(identity);\nexport const trackPageView = (options?: TrackPageViewOptions) =>\n insights.trackPageView(options);\nexport const track = (event: string, properties?: Record<string, unknown>) =>\n insights.track(event, properties);\nexport const setupObserver = () => insights.setupObserver();\n"],"names":["InsightsCommandQueue","insights","initInsights","config","enableDebugMode","disableDebugMode","identify","identity","trackPageView","options","track","event","properties","setupObserver"],"mappings":"AAKA,OAASA,oBAAoB,KAAQ,iBAAkB,CAwBvD,MAAMC,SAAW,IAAID,oBAGrB,QAAO,MAAME,aAAe,AAACC,QAC3BF,SAASC,YAAY,CAACC,OAAQ,AAChC,QAAO,MAAMC,gBAAkB,IAAMH,SAASG,eAAe,EAAG,AAChE,QAAO,MAAMC,iBAAmB,IAAMJ,SAASI,gBAAgB,EAAG,AAClE,QAAO,MAAMC,SAAW,AAACC,UACvBN,SAASK,QAAQ,CAACC,SAAU,AAC9B,QAAO,MAAMC,cAAgB,AAACC,SAC5BR,SAASO,aAAa,CAACC,QAAS,AAClC,QAAO,MAAMC,MAAQ,CAACC,MAAeC,aACnCX,SAASS,KAAK,CAACC,MAAOC,WAAY,AACpC,QAAO,MAAMC,cAAgB,IAAMZ,SAASY,aAAa,EAAG"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{describe,expect,beforeEach,afterEach,it,vi}from"vitest";import*as datalayer from"./datalayer";import*as mixpanel from"./mixpanel";import*as posthog from"./posthog";import*as logger from"./logger";import*as insights from"./index";vi.mock("./datalayer",()=>({track:vi.fn(),trackPageView:vi.fn()}));vi.mock("./mixpanel",()=>({initMixpanel:vi.fn(),enableDebugMode:vi.fn(),disableDebugMode:vi.fn(),identify:vi.fn(),trackPageView:vi.fn(),track:vi.fn(),startSessionRecording:vi.fn(),stopSessionRecording:vi.fn()}));vi.mock("./posthog",()=>({initPosthog:vi.fn(),enableDebugMode:vi.fn(),disableDebugMode:vi.fn(),identify:vi.fn(),trackPageView:vi.fn(),track:vi.fn(),startSessionRecording:vi.fn(),stopSessionRecording:vi.fn()}));vi.mock("./logger",()=>({debug:vi.fn(),info:vi.fn(),warn:vi.fn(),error:vi.fn()}));describe("Insights Command Queue",()=>{const testConfig={debug:true,mixpanelToken:"test-token",mixpanelAutoCapture:false,mixpanelRecordSessionsPercent:10,posthogApiKey:"test-key",posthogApiHost:"test-host"};const testIdentity={userId:"user-123",accountId:"account-456",organisationId:"org-789",email:"test@example.com",name:"Test User"};beforeEach(()=>{vi.clearAllMocks();vi.resetModules()});afterEach(()=>{document.body.replaceWith(document.body.cloneNode(true))});describe("Pre-initialization Queueing",()=>{it("should queue methods called before initialization",async()=>{insights.track("early_event",{early:true});insights.identify(testIdentity);insights.trackPageView();expect(mixpanel.track).not.toHaveBeenCalled();expect(posthog.track).not.toHaveBeenCalled();expect(datalayer.track).not.toHaveBeenCalled();expect(mixpanel.identify).not.toHaveBeenCalled();expect(posthog.identify).not.toHaveBeenCalled();expect(mixpanel.trackPageView).not.toHaveBeenCalled();expect(posthog.trackPageView).not.toHaveBeenCalled();expect(datalayer.trackPageView).not.toHaveBeenCalled();insights.initInsights(testConfig);expect(mixpanel.initMixpanel).toHaveBeenCalledWith(testConfig.mixpanelToken,testConfig.mixpanelAutoCapture,testConfig.debug,testConfig.mixpanelRecordSessionsPercent);expect(posthog.initPosthog).toHaveBeenCalledWith(testConfig.posthogApiKey,testConfig.posthogApiHost);expect(mixpanel.track).toHaveBeenCalledWith("early_event",{early:true});expect(posthog.track).toHaveBeenCalledWith("early_event",{early:true});expect(datalayer.track).toHaveBeenCalledWith("early_event",{early:true});expect(mixpanel.identify).toHaveBeenCalledWith(testIdentity);expect(posthog.identify).toHaveBeenCalledWith(testIdentity);expect(mixpanel.trackPageView).toHaveBeenCalled();expect(posthog.trackPageView).toHaveBeenCalled();expect(datalayer.trackPageView).not.toHaveBeenCalled()});it("should handle errors in queued methods gracefully",async()=>{mixpanel.track.mockImplementationOnce(()=>{throw new Error("Mixpanel error")});insights.track("error_event",{error:true});insights.trackPageView();insights.initInsights(testConfig);expect(logger.error).toHaveBeenCalledWith(expect.stringContaining("Failed to track event in Mixpanel"),expect.any(Error));expect(posthog.track).toHaveBeenCalledWith("error_event",{error:true});expect(datalayer.track).toHaveBeenCalledWith("error_event",{error:true});expect(mixpanel.trackPageView).toHaveBeenCalled();expect(posthog.trackPageView).toHaveBeenCalled();expect(datalayer.trackPageView).not.toHaveBeenCalled()});it("should report page view to GTM as well when includeDataLayer is true",()=>{insights.trackPageView({includeDataLayer:true});expect(mixpanel.trackPageView).toHaveBeenCalled();expect(posthog.trackPageView).toHaveBeenCalled();expect(datalayer.trackPageView).toHaveBeenCalled()})});describe("Post-initialization Direct Execution",()=>{beforeEach(()=>{insights.initInsights(testConfig);vi.clearAllMocks()});it("should directly call methods after initialization",()=>{insights.track("post_init_event",{post:true});expect(mixpanel.track).toHaveBeenCalledWith("post_init_event",{post:true});expect(posthog.track).toHaveBeenCalledWith("post_init_event",{post:true});expect(datalayer.track).toHaveBeenCalledWith("post_init_event",{post:true})});it("should handle all exported methods correctly",()=>{insights.identify(testIdentity);expect(mixpanel.identify).toHaveBeenCalledWith(testIdentity);expect(posthog.identify).toHaveBeenCalledWith(testIdentity);insights.trackPageView();expect(mixpanel.trackPageView).toHaveBeenCalled();expect(posthog.trackPageView).toHaveBeenCalled();expect(datalayer.trackPageView).not.toHaveBeenCalled();insights.startSessionRecording();expect(mixpanel.startSessionRecording).toHaveBeenCalled();expect(posthog.startSessionRecording).toHaveBeenCalled();insights.stopSessionRecording();expect(mixpanel.stopSessionRecording).toHaveBeenCalled();expect(posthog.stopSessionRecording).toHaveBeenCalled();insights.enableDebugMode();expect(mixpanel.enableDebugMode).toHaveBeenCalled();expect(posthog.enableDebugMode).toHaveBeenCalled();insights.disableDebugMode();expect(mixpanel.disableDebugMode).toHaveBeenCalled();expect(posthog.disableDebugMode).toHaveBeenCalled()});it("should report page view to GTM as well when includeDataLayer is true",()=>{insights.trackPageView({includeDataLayer:true});expect(mixpanel.trackPageView).toHaveBeenCalled();expect(posthog.trackPageView).toHaveBeenCalled();expect(datalayer.trackPageView).toHaveBeenCalled()})});describe("Observer Setup",()=>{beforeEach(()=>{insights.initInsights(testConfig);vi.clearAllMocks()});it("should set up click event observer and track clicks",()=>{const cleanup=insights.setupObserver();const testElement=document.createElement("button");testElement.setAttribute("data-insight-event","button_clicked");testElement.setAttribute("data-insight-button-id","test-123");document.body.appendChild(testElement);testElement.click();expect(mixpanel.track).toHaveBeenCalledWith("button_clicked",{buttonId:"test-123"});expect(posthog.track).toHaveBeenCalledWith("button_clicked",{buttonId:"test-123"});expect(datalayer.track).toHaveBeenCalledWith("button_clicked",{buttonId:"test-123"});cleanup();vi.clearAllMocks();testElement.click();expect(mixpanel.track).not.toHaveBeenCalled()});it("should handle nested elements correctly",()=>{insights.setupObserver();const parentElement=document.createElement("div");parentElement.setAttribute("data-insight-event","container_clicked");parentElement.setAttribute("data-insight-container-id","parent-container");const childElement=document.createElement("span");childElement.textContent="Click me";parentElement.appendChild(childElement);document.body.appendChild(parentElement);childElement.click();expect(mixpanel.track).toHaveBeenCalledWith("container_clicked",{containerId:"parent-container"})})});describe("Error Handling",()=>{it("should handle initialization errors gracefully",()=>{mixpanel.initMixpanel.mockImplementationOnce(()=>{throw new Error("Mixpanel init error")});expect(()=>{insights.initInsights(testConfig)}).not.toThrow();expect(logger.error).toHaveBeenCalledWith(expect.stringContaining("Failed to initialize Mixpanel"),expect.any(Error))});it("should handle runtime errors in methods",()=>{insights.initInsights(testConfig);vi.clearAllMocks();mixpanel.track.mockImplementationOnce(()=>{throw new Error("Mixpanel track error")});posthog.track.mockImplementationOnce(()=>{throw new Error("Posthog track error")});expect(()=>{insights.track("error_test",{test:true})}).not.toThrow();expect(logger.error).toHaveBeenCalledWith(expect.stringContaining("Failed to track event in Mixpanel"),expect.any(Error));expect(logger.error).toHaveBeenCalledWith(expect.stringContaining("Failed to track event in Posthog"),expect.any(Error));expect(datalayer.track).toHaveBeenCalledWith("error_test",{test:true})})});describe("Debug Mode",()=>{it("should respect debug flag in config",()=>{insights.initInsights(testConfig);expect(logger.debug).toHaveBeenCalledWith(expect.stringContaining("Initializing insights"));vi.clearAllMocks();insights.track("debug_test",{debug:true});expect(logger.info).toHaveBeenCalledWith(expect.stringContaining("Tracking event"),expect.objectContaining({event:"debug_test",properties:{debug:true}}))});it("should not log debug info when debug is false",()=>{insights.initInsights({...testConfig,debug:false});vi.clearAllMocks();insights.track("no_debug_test",{debug:false});expect(logger.info).not.toHaveBeenCalled()})});describe("Arbitrary Properties",()=>{const customPageViewProperties={customProperty:"custom-value",anotherProperty:123,nestedObject:{foo:"bar"}};const customPageViewPropertiesWithDataLayer={customProperty:"custom-value",anotherProperty:456};const customPageViewPropertiesWithExcludeIds={customProperty:"test",count:789};const customIdentityProperties={customProperty:"user-custom-value",userSegment:"premium",signupDate:"2024-01-01"};const customMinimalIdentityProperties={customField1:"value1",customField2:999,customField3:{nested:true}};beforeEach(()=>{insights.initInsights(testConfig);vi.clearAllMocks()});it("should pass arbitrary properties through trackPageView",()=>{insights.trackPageView(customPageViewProperties);expect(mixpanel.trackPageView).toHaveBeenCalledWith(customPageViewProperties);expect(posthog.trackPageView).toHaveBeenCalledWith(customPageViewProperties);expect(datalayer.trackPageView).not.toHaveBeenCalled()});it("should pass arbitrary properties through trackPageView with includeDataLayer",()=>{insights.trackPageView({includeDataLayer:true,...customPageViewPropertiesWithDataLayer});expect(mixpanel.trackPageView).toHaveBeenCalledWith(customPageViewPropertiesWithDataLayer);expect(posthog.trackPageView).toHaveBeenCalledWith(customPageViewPropertiesWithDataLayer);expect(datalayer.trackPageView).toHaveBeenCalledWith(customPageViewPropertiesWithDataLayer)});it("should pass arbitrary properties through trackPageView with excludeIds",()=>{const excludeIds=["id1","id2"];insights.trackPageView({excludeIds,...customPageViewPropertiesWithExcludeIds});expect(mixpanel.trackPageView).toHaveBeenCalledWith({excludeIds,...customPageViewPropertiesWithExcludeIds});expect(posthog.trackPageView).toHaveBeenCalledWith(customPageViewPropertiesWithExcludeIds)});it("should pass arbitrary properties through identify",()=>{const identityWithCustomProps={...testIdentity,...customIdentityProperties};insights.identify(identityWithCustomProps);expect(mixpanel.identify).toHaveBeenCalledWith(identityWithCustomProps);expect(posthog.identify).toHaveBeenCalledWith(identityWithCustomProps)});it("should pass arbitrary properties through identify with minimal identity",()=>{const minimalIdentity={userId:"minimal-user",accountId:"minimal-account",...customMinimalIdentityProperties};insights.identify(minimalIdentity);expect(mixpanel.identify).toHaveBeenCalledWith(minimalIdentity);expect(posthog.identify).toHaveBeenCalledWith(minimalIdentity)})})});
|
|
1
|
+
import{describe,expect,beforeEach,afterEach,it,vi}from"vitest";import*as datalayer from"./datalayer";import*as mixpanel from"./mixpanel";import*as posthog from"./posthog";import*as logger from"./logger";import*as insights from"./index";vi.mock("./datalayer",()=>({track:vi.fn(),trackPageView:vi.fn()}));vi.mock("./mixpanel",()=>({initMixpanel:vi.fn(),enableDebugMode:vi.fn(),disableDebugMode:vi.fn(),identify:vi.fn(),trackPageView:vi.fn(),track:vi.fn()}));vi.mock("./posthog",()=>({initPosthog:vi.fn(),enableDebugMode:vi.fn(),disableDebugMode:vi.fn(),identify:vi.fn(),trackPageView:vi.fn(),track:vi.fn()}));vi.mock("./logger",()=>({debug:vi.fn(),info:vi.fn(),warn:vi.fn(),error:vi.fn()}));describe("Insights Command Queue",()=>{const testConfig={debug:true,mixpanelToken:"test-token",mixpanelAutoCapture:false,posthogApiKey:"test-key",posthogApiHost:"test-host"};const testIdentity={userId:"user-123",accountId:"account-456",organisationId:"org-789",email:"test@example.com",name:"Test User"};beforeEach(()=>{vi.clearAllMocks();vi.resetModules()});afterEach(()=>{document.body.replaceWith(document.body.cloneNode(true))});describe("Pre-initialization Queueing",()=>{it("should queue methods called before initialization",async()=>{insights.track("early_event",{early:true});insights.identify(testIdentity);insights.trackPageView();expect(mixpanel.track).not.toHaveBeenCalled();expect(posthog.track).not.toHaveBeenCalled();expect(datalayer.track).not.toHaveBeenCalled();expect(mixpanel.identify).not.toHaveBeenCalled();expect(posthog.identify).not.toHaveBeenCalled();expect(mixpanel.trackPageView).not.toHaveBeenCalled();expect(posthog.trackPageView).not.toHaveBeenCalled();expect(datalayer.trackPageView).not.toHaveBeenCalled();insights.initInsights(testConfig);expect(mixpanel.initMixpanel).toHaveBeenCalledWith(testConfig.mixpanelToken,testConfig.mixpanelAutoCapture,testConfig.debug,undefined);expect(posthog.initPosthog).toHaveBeenCalledWith(testConfig.posthogApiKey,testConfig.posthogApiHost);expect(mixpanel.track).toHaveBeenCalledWith("early_event",{early:true});expect(posthog.track).toHaveBeenCalledWith("early_event",{early:true});expect(datalayer.track).toHaveBeenCalledWith("early_event",{early:true});expect(mixpanel.identify).toHaveBeenCalledWith(testIdentity);expect(posthog.identify).toHaveBeenCalledWith(testIdentity);expect(mixpanel.trackPageView).toHaveBeenCalled();expect(posthog.trackPageView).toHaveBeenCalled();expect(datalayer.trackPageView).not.toHaveBeenCalled()});it("should handle errors in queued methods gracefully",async()=>{mixpanel.track.mockImplementationOnce(()=>{throw new Error("Mixpanel error")});insights.track("error_event",{error:true});insights.trackPageView();insights.initInsights(testConfig);expect(logger.error).toHaveBeenCalledWith(expect.stringContaining("Failed to track event in Mixpanel"),expect.any(Error));expect(posthog.track).toHaveBeenCalledWith("error_event",{error:true});expect(datalayer.track).toHaveBeenCalledWith("error_event",{error:true});expect(mixpanel.trackPageView).toHaveBeenCalled();expect(posthog.trackPageView).toHaveBeenCalled();expect(datalayer.trackPageView).not.toHaveBeenCalled()});it("should report page view to GTM as well when includeDataLayer is true",()=>{insights.trackPageView({includeDataLayer:true});expect(mixpanel.trackPageView).toHaveBeenCalled();expect(posthog.trackPageView).toHaveBeenCalled();expect(datalayer.trackPageView).toHaveBeenCalled()})});describe("Post-initialization Direct Execution",()=>{beforeEach(()=>{insights.initInsights(testConfig);vi.clearAllMocks()});it("should directly call methods after initialization",()=>{insights.track("post_init_event",{post:true});expect(mixpanel.track).toHaveBeenCalledWith("post_init_event",{post:true});expect(posthog.track).toHaveBeenCalledWith("post_init_event",{post:true});expect(datalayer.track).toHaveBeenCalledWith("post_init_event",{post:true})});it("should handle all exported methods correctly",()=>{insights.identify(testIdentity);expect(mixpanel.identify).toHaveBeenCalledWith(testIdentity);expect(posthog.identify).toHaveBeenCalledWith(testIdentity);insights.trackPageView();expect(mixpanel.trackPageView).toHaveBeenCalled();expect(posthog.trackPageView).toHaveBeenCalled();expect(datalayer.trackPageView).not.toHaveBeenCalled();insights.enableDebugMode();expect(mixpanel.enableDebugMode).toHaveBeenCalled();expect(posthog.enableDebugMode).toHaveBeenCalled();insights.disableDebugMode();expect(mixpanel.disableDebugMode).toHaveBeenCalled();expect(posthog.disableDebugMode).toHaveBeenCalled()});it("should report page view to GTM as well when includeDataLayer is true",()=>{insights.trackPageView({includeDataLayer:true});expect(mixpanel.trackPageView).toHaveBeenCalled();expect(posthog.trackPageView).toHaveBeenCalled();expect(datalayer.trackPageView).toHaveBeenCalled()})});describe("Observer Setup",()=>{beforeEach(()=>{insights.initInsights(testConfig);vi.clearAllMocks()});it("should set up click event observer and track clicks",()=>{const cleanup=insights.setupObserver();const testElement=document.createElement("button");testElement.setAttribute("data-insight-event","button_clicked");testElement.setAttribute("data-insight-button-id","test-123");document.body.appendChild(testElement);testElement.click();expect(mixpanel.track).toHaveBeenCalledWith("button_clicked",{buttonId:"test-123"});expect(posthog.track).toHaveBeenCalledWith("button_clicked",{buttonId:"test-123"});expect(datalayer.track).toHaveBeenCalledWith("button_clicked",{buttonId:"test-123"});cleanup();vi.clearAllMocks();testElement.click();expect(mixpanel.track).not.toHaveBeenCalled()});it("should handle nested elements correctly",()=>{insights.setupObserver();const parentElement=document.createElement("div");parentElement.setAttribute("data-insight-event","container_clicked");parentElement.setAttribute("data-insight-container-id","parent-container");const childElement=document.createElement("span");childElement.textContent="Click me";parentElement.appendChild(childElement);document.body.appendChild(parentElement);childElement.click();expect(mixpanel.track).toHaveBeenCalledWith("container_clicked",{containerId:"parent-container"})})});describe("Error Handling",()=>{it("should handle initialization errors gracefully",()=>{mixpanel.initMixpanel.mockImplementationOnce(()=>{throw new Error("Mixpanel init error")});expect(()=>{insights.initInsights(testConfig)}).not.toThrow();expect(logger.error).toHaveBeenCalledWith(expect.stringContaining("Failed to initialize Mixpanel"),expect.any(Error))});it("should handle runtime errors in methods",()=>{insights.initInsights(testConfig);vi.clearAllMocks();mixpanel.track.mockImplementationOnce(()=>{throw new Error("Mixpanel track error")});posthog.track.mockImplementationOnce(()=>{throw new Error("Posthog track error")});expect(()=>{insights.track("error_test",{test:true})}).not.toThrow();expect(logger.error).toHaveBeenCalledWith(expect.stringContaining("Failed to track event in Mixpanel"),expect.any(Error));expect(logger.error).toHaveBeenCalledWith(expect.stringContaining("Failed to track event in Posthog"),expect.any(Error));expect(datalayer.track).toHaveBeenCalledWith("error_test",{test:true})})});describe("Debug Mode",()=>{it("should respect debug flag in config",()=>{insights.initInsights(testConfig);expect(logger.debug).toHaveBeenCalledWith(expect.stringContaining("Initializing insights"));vi.clearAllMocks();insights.track("debug_test",{debug:true});expect(logger.info).toHaveBeenCalledWith(expect.stringContaining("Tracking event"),expect.objectContaining({event:"debug_test",properties:{debug:true}}))});it("should not log debug info when debug is false",()=>{insights.initInsights({...testConfig,debug:false});vi.clearAllMocks();insights.track("no_debug_test",{debug:false});expect(logger.info).not.toHaveBeenCalled()})});describe("Arbitrary Properties",()=>{const customPageViewProperties={customProperty:"custom-value",anotherProperty:123,nestedObject:{foo:"bar"}};const customPageViewPropertiesWithDataLayer={customProperty:"custom-value",anotherProperty:456};const customPageViewPropertiesWithExcludeIds={customProperty:"test",count:789};const customIdentityProperties={customProperty:"user-custom-value",userSegment:"premium",signupDate:"2024-01-01"};const customMinimalIdentityProperties={customField1:"value1",customField2:999,customField3:{nested:true}};beforeEach(()=>{insights.initInsights(testConfig);vi.clearAllMocks()});it("should pass arbitrary properties through trackPageView",()=>{insights.trackPageView(customPageViewProperties);expect(mixpanel.trackPageView).toHaveBeenCalledWith(customPageViewProperties);expect(posthog.trackPageView).toHaveBeenCalledWith(customPageViewProperties);expect(datalayer.trackPageView).not.toHaveBeenCalled()});it("should pass arbitrary properties through trackPageView with includeDataLayer",()=>{insights.trackPageView({includeDataLayer:true,...customPageViewPropertiesWithDataLayer});expect(mixpanel.trackPageView).toHaveBeenCalledWith(customPageViewPropertiesWithDataLayer);expect(posthog.trackPageView).toHaveBeenCalledWith(customPageViewPropertiesWithDataLayer);expect(datalayer.trackPageView).toHaveBeenCalledWith(customPageViewPropertiesWithDataLayer)});it("should pass arbitrary properties through trackPageView with excludeIds",()=>{const excludeIds=["id1","id2"];insights.trackPageView({excludeIds,...customPageViewPropertiesWithExcludeIds});expect(mixpanel.trackPageView).toHaveBeenCalledWith({excludeIds,...customPageViewPropertiesWithExcludeIds});expect(posthog.trackPageView).toHaveBeenCalledWith(customPageViewPropertiesWithExcludeIds)});it("should pass arbitrary properties through identify",()=>{const identityWithCustomProps={...testIdentity,...customIdentityProperties};insights.identify(identityWithCustomProps);expect(mixpanel.identify).toHaveBeenCalledWith(identityWithCustomProps);expect(posthog.identify).toHaveBeenCalledWith(identityWithCustomProps)});it("should pass arbitrary properties through identify with minimal identity",()=>{const minimalIdentity={userId:"minimal-user",accountId:"minimal-account",...customMinimalIdentityProperties};insights.identify(minimalIdentity);expect(mixpanel.identify).toHaveBeenCalledWith(minimalIdentity);expect(posthog.identify).toHaveBeenCalledWith(minimalIdentity)})})});
|
|
2
2
|
//# sourceMappingURL=index.test.js.map
|