Repository: tjtanjin/react-chatbotify (ts)

Issue:
[Task] Add unit test cases for mediaFileParser utils
**Task Description:**
Currently, we run unit tests using jest which can be found [**here**](https://github.com/tjtanjin/react-chatbotify/tree/main/__tests__). 

However, the test cases are not yet comprehensive enough and work can be done to cover more grounds. Specifically for this issue, we hope to add test cases for the `mediaFileParser` utils.

**Deliverable(s):**
New unit test cases that cover the testing of the `mediaFileParser`.

**Additional Context:**
- The `mediaFileParser` can be found [**here**](https://github.com/tjtanjin/react-chatbotify/tree/main/src/utils/mediaFileParser.ts)
- An example for `messageBuilder` can be found [**here**](https://github.com/tjtanjin/react-chatbotify/blob/main/__tests__/utils/messageBuilder.test.ts) which may serve as a good source for reference


Pull request description:
Add test cases for mediaFileParser utils
#### Description

Please include a brief summary of the change and include the relevant issue(s).

Closes #199 

#### What change does this PR introduce?

Please select the relevant option(s).

- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Documentation update (changes to docs/code comments)
- [x] Unit test

#### What is the proposed approach?

Please give a short overview/explanation on the approach taken to resolve the issue(s).

#### Checklist:

- [x] The commit message follows our adopted [guidelines](https://www.conventionalcommits.org/en/v1.0.0/)
- [x] Testing has been done for the change(s) added (for bug fixes/features)
- [x] Relevant comments/docs have been added/updated (for bug fixes/features)

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b653216..098d8f1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,19 @@
 # CHANGELOG.md
 
+## v2.0.0-beta.19 (18-10-2024)
+
+**Fixed:**
+- Fixed an issue where using `ChatBotProvider` for hooks will cause infinite re-renders
+- Fixed an issue where audio service will read out html tags
+- Fixed an issue where `sendOutput` field is being ignored for `options` and `checkboxes` attributes
+- Fixed an issue where tooltip had a much lower z-index than chat window, causing it to be partially hidden
+- Fixed an issue with the `useToasts` hook not being imported correctly
+- Fixed an issue where chatbot button svg icon was not rendered correctly on mobile devices
+
+**Added:**
+- Added a new `updateSettings` utility function to the `useSettings` hook (refer to documentation for details)
+- Added a new `updateStyles` utility function to the `useStyles` hook (refer to documentation for details)
+
 ## v2.0.0-beta.18 (14-10-2024)
 
 **Fixed:**
diff --git a/package-lock.json b/package-lock.json
index 05bddd0..7311054 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "react-chatbotify",
-  "version": "2.0.0-beta.18",
+  "version": "2.0.0-beta.19",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "react-chatbotify",
-      "version": "2.0.0-beta.18",
+      "version": "2.0.0-beta.19",
       "license": "MIT",
       "devDependencies": {
         "@testing-library/jest-dom": "^6.5.0",
diff --git a/package.json b/package.json
index 5b4fba7..78df543 100644
--- a/package.json
+++ b/package.json
@@ -12,7 +12,7 @@
   "files": [
     "./dist"
   ],
-  "version": "2.0.0-beta.18",
+  "version": "2.0.0-beta.19",
   "description": "A modern React library for creating flexible and extensible chatbots.",
   "type": "module",
   "main": "./dist/index.cjs",
diff --git a/src/assets/chat_icon.svg b/src/assets/chat_icon.svg
index 31635b2..65bacf2 100644
--- a/src/assets/chat_icon.svg
+++ b/src/assets/chat_icon.svg
@@ -1,1 +1,1 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 637 637"><path d="m478 113-13 2-4 1h4l14-2c10-2 35-2 42-1l6 3 3 1c3 0-4-3-8-5-7-1-34-1-44 1m56 6 8 6c7 5 20 17 20 19s7 12 8 12l-3-6-4-6c0-3-15-17-25-24-5-3-8-4-4-1m-198 16-9 1a512 512 0 0 1 9-1m-180 30a412 412 0 0 0-23 4 167 167 0 0 0-32 8 439 439 0 0 1 46-9 304 304 0 0 1 11-2l6-1h-8m369 8 4 3-7-6c-1 0 0 2 3 3m-439 9c-7 3-18 13-18 15l6-5c5-5 9-8 14-10l6-4-8 4m442 12-10 11-7 6 7-6c7-5 14-14 13-16l-3 5M65 205l-1 11c0 9 0 10 4 17 5 10 7 12 22 11 10 0 15 0 38-5l5-1-6 1-16 2-21 2c-13 1-16-1-21-10-3-5-4-6-4-15l1-12c1-4 1-5-1-1m335 1c3 0 3 1 3 3l-2 6c-2 5-35 39-76 80-53 51-120 117-138 137-21 24-22 25-22 27 0 3 6 9 9 9 4 0 14-8 38-27a5073 5073 0 0 1 15-16 1421 1421 0 0 0-17 15l-31 25-6 2-4-3c-3-3-3-4-2-6 3-8 79-85 160-163 57-57 79-80 79-86 0-2-3-4-7-4-2 0-2 0 1 1m165 18c-9 17-25 32-57 53l-10 8 16-11 12-8c15-10 27-22 39-39 5-7 6-8 4-8l-4 5m-221 2a199 199 0 0 0-19 10 183 183 0 0 1 27-12c0-1-1-1-8 2m-34 16c-3 3-2 3 2 0l1-2-3 2m151 3-1 2 2-2c3-3 2-3-1 0m-16 12-11 10a281 281 0 0 0 11-10m-24 18-7 5-7 5-42 31c-10 7-24 19-22 19l7-6 16-12a343 343 0 0 0 35-25 573 573 0 0 0 20-17m-187 0-2 2 4-2 3-2-5 2m-14 6-3 2 4-1 4-3-5 2m-7 3-8 4-18 8 18-7 7-3 3-1c1-2 0-2-2-1m254 24a2672 2672 0 0 1-71 54 601 601 0 0 0 39-29 1352 1352 0 0 1 32-25m-291-8-2 2 4-2 3-2-5 2m-13 5-2 2h3c5-3 4-4-1-2m-19 9-7 3-13 6c-10 5-13 7-14 10-3 4-2 5 1 0 2-3 5-5 14-9l13-6 12-6-6 2m178 36c-17 12-21 16-21 17a390 390 0 0 1 20-17m-206 34 1 4 1 3 1 5 2 8 1 5v-6l-1-8-2-5-1-4-1-2c-1-2-1-2-1 0m222 23-9 7a683 683 0 0 0-56 44 261 261 0 0 0-26 20 1345 1345 0 0 0-46 34c-6 3-7 3-19 3-11 0-12 0-18-3-8-4-10-8-17-20l-5-10 4 10c5 10 12 18 19 22 4 2 6 2 17 2 13 0 14 0 21-3l26-19 42-33a2183 2183 0 0 0 43-34 643 643 0 0 1 24-20" fill="#fff" fill-rule="evenodd"/><path d="M478 114a620 620 0 0 1-39 6l-21 3a5481 5481 0 0 0-19 3 870 870 0 0 1-54 8l-1 1-6 1-12 2-13 2a992 992 0 0 0-76 12 400 400 0 0 1-45 8l-6 1-5 1h-3l-3 1-2 1-26 4-13 2-17 3c-23 5-33 9-43 19-7 8-9 13-9 27 0 8 1 9 4 14 5 9 8 11 21 10l21-2a1133 1133 0 0 1 23-4 659 659 0 0 0 74-14l3-1h3l3-1h3l2-1 2-1h3l2-1h2l24-5 38-7a2100 2100 0 0 1 93-18l3-1 10-1a2082 2082 0 0 1 120-17l2 1c2 0 8 5 8 7l1 2 1 6c0 4 0 6-3 10-2 4-16 16-18 16l-7 5-10 8a1070 1070 0 0 0-11 7 113 113 0 0 1-20 14l-2 2-3 2-2 2h-1a154 154 0 0 1-28 21l-5 4-5 4a573 573 0 0 1-50 37 175 175 0 0 0-24 18l-1 2-2 1-2 1-2 1-1 1-12 10a396 396 0 0 0-32 25l-8 6-6 5-9 7a326 326 0 0 1-25 20l-13 12a5073 5073 0 0 0-17 15c-24 20-34 27-39 27-2 0-8-6-8-9 0-2 1-3 22-27 18-20 85-86 138-137 41-41 74-75 76-80l2-6c0-2 0-3-2-3l-7 1a233 233 0 0 1-26 9l-1 1-1 1h-1l-1 1h-2l-3 2-1 1h-2l-3 2h-1l-5 2-22 10-9 4-4 2-5 2-10 4-2 1-2 1-2 1-13 6a334 334 0 0 1-38 17 75 75 0 0 0-14 6l-5 2-4 1-2 1-3 2-7 3-16 6a247 247 0 0 0-32 14l-1 1h-2l-1 1h-2l-1 1-12 6a103 103 0 0 0-27 15l-2 5-1 1v11l1 5 1 5 1 4c0 3 0 4 2 4v3l1 3 1 2 2 10 1 4 1 5 2 10 2 8 1 4 1 4c3 3 9 25 11 38l1 7v2l1 4v1l1 1v2l1 2 1 3v1l5 10c7 13 9 16 17 20 6 3 7 3 18 3 12 0 13 0 19-3 8-4 11-6 26-18a1345 1345 0 0 1 85-67 683 683 0 0 1 50-39l12-9a513 513 0 0 1 44-33 201 201 0 0 0 22-17 2270 2270 0 0 0 73-54 158 158 0 0 0 58-59c2 0 3-9 4-27l-1-19-1-4-1-6-1-3-4-6-4-6c0-2-6-8-13-14a77 77 0 0 0-28-18c-6-1-34-1-43 1" fill="#fbfbfb" fill-rule="evenodd"/></svg>
\ No newline at end of file
+<svg viewBox="0 0 637 637"><path d="m478 113-13 2-4 1h4l14-2c10-2 35-2 42-1l6 3 3 1c3 0-4-3-8-5-7-1-34-1-44 1m56 6 8 6c7 5 20 17 20 19s7 12 8 12l-3-6-4-6c0-3-15-17-25-24-5-3-8-4-4-1m-198 16-9 1a512 512 0 0 1 9-1m-180 30a412 412 0 0 0-23 4 167 167 0 0 0-32 8 439 439 0 0 1 46-9 304 304 0 0 1 11-2l6-1h-8m369 8 4 3-7-6c-1 0 0 2 3 3m-439 9c-7 3-18 13-18 15l6-5c5-5 9-8 14-10l6-4-8 4m442 12-10 11-7 6 7-6c7-5 14-14 13-16l-3 5M65 205l-1 11c0 9 0 10 4 17 5 10 7 12 22 11 10 0 15 0 38-5l5-1-6 1-16 2-21 2c-13 1-16-1-21-10-3-5-4-6-4-15l1-12c1-4 1-5-1-1m335 1c3 0 3 1 3 3l-2 6c-2 5-35 39-76 80-53 51-120 117-138 137-21 24-22 25-22 27 0 3 6 9 9 9 4 0 14-8 38-27a5073 5073 0 0 1 15-16 1421 1421 0 0 0-17 15l-31 25-6 2-4-3c-3-3-3-4-2-6 3-8 79-85 160-163 57-57 79-80 79-86 0-2-3-4-7-4-2 0-2 0 1 1m165 18c-9 17-25 32-57 53l-10 8 16-11 12-8c15-10 27-22 39-39 5-7 6-8 4-8l-4 5m-221 2a199 199 0 0 0-19 10 183 183 0 0 1 27-12c0-1-1-1-8 2m-34 16c-3 3-2 3 2 0l1-2-3 2m151 3-1 2 2-2c3-3 2-3-1 0m-16 12-11 10a281 281 0 0 0 11-10m-24 18-7 5-7 5-42 31c-10 7-24 19-22 19l7-6 16-12a343 343 0 0 0 35-25 573 573 0 0 0 20-17m-187 0-2 2 4-2 3-2-5 2m-14 6-3 2 4-1 4-3-5 2m-7 3-8 4-18 8 18-7 7-3 3-1c1-2 0-2-2-1m254 24a2672 2672 0 0 1-71 54 601 601 0 0 0 39-29 1352 1352 0 0 1 32-25m-291-8-2 2 4-2 3-2-5 2m-13 5-2 2h3c5-3 4-4-1-2m-19 9-7 3-13 6c-10 5-13 7-14 10-3 4-2 5 1 0 2-3 5-5 14-9l13-6 12-6-6 2m178 36c-17 12-21 16-21 17a390 390 0 0 1 20-17m-206 34 1 4 1 3 1 5 2 8 1 5v-6l-1-8-2-5-1-4-1-2c-1-2-1-2-1 0m222 23-9 7a683 683 0 0 0-56 44 261 261 0 0 0-26 20 1345 1345 0 0 0-46 34c-6 3-7 3-19 3-11 0-12 0-18-3-8-4-10-8-17-20l-5-10 4 10c5 10 12 18 19 22 4 2 6 2 17 2 13 0 14 0 21-3l26-19 42-33a2183 2183 0 0 0 43-34 643 643 0 0 1 24-20" fill="#fff" fill-rule="evenodd"/><path d="M478 114a620 620 0 0 1-39 6l-21 3a5481 5481 0 0 0-19 3 870 870 0 0 1-54 8l-1 1-6 1-12 2-13 2a992 992 0 0 0-76 12 400 400 0 0 1-45 8l-6 1-5 1h-3l-3 1-2 1-26 4-13 2-17 3c-23 5-33 9-43 19-7 8-9 13-9 27 0 8 1 9 4 14 5 9 8 11 21 10l21-2a1133 1133 0 0 1 23-4 659 659 0 0 0 74-14l3-1h3l3-1h3l2-1 2-1h3l2-1h2l24-5 38-7a2100 2100 0 0 1 93-18l3-1 10-1a2082 2082 0 0 1 120-17l2 1c2 0 8 5 8 7l1 2 1 6c0 4 0 6-3 10-2 4-16 16-18 16l-7 5-10 8a1070 1070 0 0 0-11 7 113 113 0 0 1-20 14l-2 2-3 2-2 2h-1a154 154 0 0 1-28 21l-5 4-5 4a573 573 0 0 1-50 37 175 175 0 0 0-24 18l-1 2-2 1-2 1-2 1-1 1-12 10a396 396 0 0 0-32 25l-8 6-6 5-9 7a326 326 0 0 1-25 20l-13 12a5073 5073 0 0 0-17 15c-24 20-34 27-39 27-2 0-8-6-8-9 0-2 1-3 22-27 18-20 85-86 138-137 41-41 74-75 76-80l2-6c0-2 0-3-2-3l-7 1a233 233 0 0 1-26 9l-1 1-1 1h-1l-1 1h-2l-3 2-1 1h-2l-3 2h-1l-5 2-22 10-9 4-4 2-5 2-10 4-2 1-2 1-2 1-13 6a334 334 0 0 1-38 17 75 75 0 0 0-14 6l-5 2-4 1-2 1-3 2-7 3-16 6a247 247 0 0 0-32 14l-1 1h-2l-1 1h-2l-1 1-12 6a103 103 0 0 0-27 15l-2 5-1 1v11l1 5 1 5 1 4c0 3 0 4 2 4v3l1 3 1 2 2 10 1 4 1 5 2 10 2 8 1 4 1 4c3 3 9 25 11 38l1 7v2l1 4v1l1 1v2l1 2 1 3v1l5 10c7 13 9 16 17 20 6 3 7 3 18 3 12 0 13 0 19-3 8-4 11-6 26-18a1345 1345 0 0 1 85-67 683 683 0 0 1 50-39l12-9a513 513 0 0 1 44-33 201 201 0 0 0 22-17 2270 2270 0 0 0 73-54 158 158 0 0 0 58-59c2 0 3-9 4-27l-1-19-1-4-1-6-1-3-4-6-4-6c0-2-6-8-13-14a77 77 0 0 0-28-18c-6-1-34-1-43 1"/></svg>
\ No newline at end of file
diff --git a/src/components/Buttons/FileAttachmentButton/FileAttachmentButton.tsx b/src/components/Buttons/FileAttachmentButton/FileAttachmentButton.tsx
index d6537d2..a222849 100644
--- a/src/components/Buttons/FileAttachmentButton/FileAttachmentButton.tsx
+++ b/src/components/Buttons/FileAttachmentButton/FileAttachmentButton.tsx
@@ -2,7 +2,7 @@ import { ChangeEvent } from "react";
 
 import MediaDisplay from "../../ChatBotBody/MediaDisplay/MediaDisplay";
 import { getMediaFileDetails } from "../../../utils/mediaFileParser";
-import { useToastInternal } from "../../../hooks/internal/useToastsInternal";
+import { useToastsInternal } from "../../../hooks/internal/useToastsInternal";
 import { useChatWindowInternal } from "../../../hooks/internal/useChatWindowInternal";
 import { useSubmitInputInternal } from "../../../hooks/internal/useSubmitInputInternal";
 import { useMessagesInternal } from "../../../hooks/internal/useMessagesInternal";
@@ -38,7 +38,7 @@ const FileAttachmentButton = () => {
 	const flow = flowRef.current as Flow;
 
 	// handles toasts
-	const { showToast, dismissToast } = useToastInternal();
+	const { showToast, dismissToast } = useToastsInternal();
 	
 	// handles rcb events
 	const { callRcbEvent } = useRcbEventInternal();
diff --git a/src/components/ChatBot.tsx b/src/components/ChatBot.tsx
index 22c32e4..b141364 100644
--- a/src/components/ChatBot.tsx
+++ b/src/components/ChatBot.tsx
@@ -1,4 +1,4 @@
-import { useMemo, useRef, useState } from "react";
+import { useEffect, useMemo, useRef, useState } from "react";
 
 import ChatBotContainer from "./ChatBotContainer";
 import ChatBotLoader from "./ChatBotLoader";
@@ -26,8 +26,8 @@ const ChatBot = ({
 	flow,
 	settings,
 	styles,
-	themes = [],
-	plugins = []
+	themes,
+	plugins,
 }: {
 	id?: string;
 	flow?: Flow;
@@ -42,6 +42,7 @@ const ChatBot = ({
 	const finalFlow = (!flow || Object.keys(flow).length === 0) ? WelcomeFlow : flow;
 	const finalSettings = !settings ? {} : settings;
 	const finalStyles = !styles ? {} : styles;
+	const finalPlugins = !plugins ? [] : plugins;
 
 	// handles loading of chatbot only when config is loaded
 	const [configLoaded, setConfigLoaded] = useState<boolean>(false);
@@ -52,13 +53,21 @@ const ChatBot = ({
 	// handles style root container ref
 	const styleRootRef = useRef<HTMLStyleElement | null>(null);
 
+	// special handling for themes to be a state, to facilitate users updating themes directly
+	const [finalThemes, setFinalThemes] = useState<Theme | Array<Theme>>(!themes ? []: themes);
+	useEffect(() => {
+		if (themes) {
+			setFinalThemes(themes);
+		}
+	}, [themes])
+
 	/**
 	 * Renders chatbot with provider depending on whether one was provided by the user.
 	 */
 	const renderChatBot = () => (
 		<>
 			<ChatBotLoader styleRootRef={styleRootRef} id={finalBotId} flow={finalFlow} settings={finalSettings}
-				styles={finalStyles} themes={themes} plugins={plugins} setConfigLoaded={setConfigLoaded}
+				styles={finalStyles} themes={finalThemes} plugins={finalPlugins} setConfigLoaded={setConfigLoaded}
 			/>
 			{configLoaded && <ChatBotContainer plugins={plugins} />}
 		</>
diff --git a/src/components/ChatBotBody/ToastPrompt/ToastPrompt.tsx b/src/components/ChatBotBody/ToastPrompt/ToastPrompt.tsx
index 21d1b12..08d7c1b 100644
--- a/src/components/ChatBotBody/ToastPrompt/ToastPrompt.tsx
+++ b/src/components/ChatBotBody/ToastPrompt/ToastPrompt.tsx
@@ -1,6 +1,6 @@
 import { useEffect, useState, MouseEvent } from "react";
 
-import { useToastInternal } from "../../../hooks/internal/useToastsInternal";
+import { useToastsInternal } from "../../../hooks/internal/useToastsInternal";
 import { useSettingsContext } from "../../../context/SettingsContext";
 import { useStylesContext } from "../../../context/StylesContext";
 
@@ -30,7 +30,7 @@ const Toast = ({
 	const { styles } = useStylesContext();
 
 	// handles toasts
-	const { dismissToast } = useToastInternal();
+	const { dismissToast } = useToastsInternal();
 
 	// tracks if toast prompt is hovered
 	const [isHovered, setIsHovered] = useState<boolean>(false);
diff --git a/src/components/ChatBotButton/ChatBotButton.css b/src/components/ChatBotButton/ChatBotButton.css
index 6a29421..df6943a 100644
--- a/src/components/ChatBotButton/ChatBotButton.css
+++ b/src/components/ChatBotButton/ChatBotButton.css
@@ -33,8 +33,8 @@
 	display: flex;
 	justify-content: center;
 	align-items: center;
-	width: 100%;
-	height: 100%;
+	width: 75px;
+	height: 75px;
 	background-position: center;
 	background-size: cover;
 	background-repeat: no-repeat;
diff --git a/src/components/ChatBotButton/ChatBotButton.tsx b/src/components/ChatBotButton/ChatBotButton.tsx
index e35c20c..506d509 100644
--- a/src/components/ChatBotButton/ChatBotButton.tsx
+++ b/src/components/ChatBotButton/ChatBotButton.tsx
@@ -31,6 +31,7 @@ const ChatBotButton = () => {
 	// styles for chat icon
 	const chatIconStyle: React.CSSProperties = {
 		backgroundImage: `url(${settings.chatButton?.icon})`,
+		fill: "#fff",
 		...styles.chatIconStyle
 	};
 
diff --git a/src/hooks/internal/useBotEffectsInternal.tsx b/src/hooks/internal/useBotEffectsInternal.tsx
index bbbae2c..7e440e8 100644
--- a/src/hooks/internal/useBotEffectsInternal.tsx
+++ b/src/hooks/internal/useBotEffectsInternal.tsx
@@ -13,7 +13,7 @@ import { useChatHistoryInternal } from "./useChatHistoryInternal";
 import { usePathsInternal } from "./usePathsInternal";
 import { useTextAreaInternal } from "./useTextAreaInternal";
 import { useMessagesInternal } from "./useMessagesInternal";
-import { useToastInternal } from "./useToastsInternal";
+import { useToastsInternal } from "./useToastsInternal";
 import { useVoiceInternal } from "./useVoiceInternal";
 import { useSettingsContext } from "../../context/SettingsContext";
 import { useBotStatesContext } from "../../context/BotStatesContext";
@@ -46,7 +46,7 @@ export const useBotEffectInternal = () => {
 	const { getCurrPath, getPrevPath, goToPath, paths } = usePathsInternal();
 
 	// handles toast
-	const { showToast, dismissToast } = useToastInternal();
+	const { showToast, dismissToast } = useToastsInternal();
 
 	// handles bot states
 	const {
diff --git a/src/hooks/internal/useSettingsInternal.ts b/src/hooks/internal/useSettingsInternal.ts
new file mode 100644
index 0000000..c1231f8
--- /dev/null
+++ b/src/hooks/internal/useSettingsInternal.ts
@@ -0,0 +1,26 @@
+import { deepClone, getCombinedConfig } from "../../utils/configParser";
+import { useSettingsContext } from "../../context/SettingsContext";
+import { Settings } from "../../types/Settings";
+
+/**
+ * Internal custom hook for managing settings.
+ */
+export const useSettingsInternal = () => {
+	// handles settings
+	const { settings, setSettings } = useSettingsContext();
+
+    /**
+     * Updates the settings for the chatbot.
+     *
+     * @param fields fields to update
+     */
+    const updateSettings = (fields: object) => {
+        setSettings(deepClone(getCombinedConfig(fields, settings) as Settings));
+    }
+
+	return {
+		settings,
+		setSettings,
+        updateSettings
+	};
+};
diff --git a/src/hooks/internal/useStylesInternal.ts b/src/hooks/internal/useStylesInternal.ts
new file mode 100644
index 0000000..88a0d45
--- /dev/null
+++ b/src/hooks/internal/useStylesInternal.ts
@@ -0,0 +1,26 @@
+import { deepClone, getCombinedConfig } from "../../utils/configParser";
+import { useStylesContext } from "../../context/StylesContext";
+import { Styles } from "../../types/Styles";
+
+/**
+ * Internal custom hook for managing styles.
+ */
+export const useStylesInternal = () => {
+	// handles styles
+	const { styles, setStyles } = useStylesContext();
+
+    /**
+     * Updates the styles for the chatbot.
+     *
+     * @param fields fields to update
+     */
+    const updateStyles = (fields: object) => {
+        setStyles(deepClone(getCombinedConfig(fields, styles) as Styles));
+    }
+
+	return {
+		styles,
+		setStyles,
+        updateStyles
+	};
+};
diff --git a/src/hooks/internal/useSubmitInputInternal.ts b/src/hooks/internal/useSubmitInputInternal.ts
index ae0bb61..5f09644 100644
--- a/src/hooks/internal/useSubmitInputInternal.ts
+++ b/src/hooks/internal/useSubmitInputInternal.ts
@@ -8,7 +8,7 @@ import { useRcbEventInternal } from "./useRcbEventInternal";
 import { useVoiceInternal } from "./useVoiceInternal";
 import { useTextAreaInternal } from "./useTextAreaInternal";
 import { useChatWindowInternal } from "./useChatWindowInternal";
-import { useToastInternal } from "./useToastsInternal";
+import { useToastsInternal } from "./useToastsInternal";
 import { useBotStatesContext } from "../../context/BotStatesContext";
 import { useBotRefsContext } from "../../context/BotRefsContext";
 import { useSettingsContext } from "../../context/SettingsContext";
@@ -44,7 +44,7 @@ export const useSubmitInputInternal = () => {
 	const { flowRef, chatBodyRef, inputRef, keepVoiceOnRef, paramsInputRef } = useBotRefsContext();
 
 	// handles toasts
-	const { showToast, dismissToast } = useToastInternal();
+	const { showToast, dismissToast } = useToastsInternal();
 
 	// handles rcb events
 	const { callRcbEvent } = useRcbEventInternal();
diff --git a/src/hooks/internal/useToastsInternal.ts b/src/hooks/internal/useToastsInternal.ts
index cac9f90..cb6aa46 100644
--- a/src/hooks/internal/useToastsInternal.ts
+++ b/src/hooks/internal/useToastsInternal.ts
@@ -10,7 +10,7 @@ import { RcbEvent } from "../../constants/RcbEvent";
 /**
  * Internal custom hook for managing toasts.
  */
-export const useToastInternal = () => {
+export const useToastsInternal = () => {
 	// handles settings
 	const { settings } = useSettingsContext();
 
diff --git a/src/hooks/useSettings.ts b/src/hooks/useSettings.ts
index f515158..317f4b9 100644
--- a/src/hooks/useSettings.ts
+++ b/src/hooks/useSettings.ts
@@ -1,14 +1,15 @@
-import { useSettingsContext } from "../context/SettingsContext";
+import { useSettingsInternal } from "./internal/useSettingsInternal";
 
 /**
  * External custom hook for managing settings.
  */
 export const useSettings = () => {
 	// handles settings
-	const { settings, setSettings } = useSettingsContext();
+	const { settings, setSettings, updateSettings } = useSettingsInternal();
 
 	return {
 		settings,
-		setSettings
+		setSettings,
+		updateSettings
 	};
 };
diff --git a/src/hooks/useStyles.ts b/src/hooks/useStyles.ts
index b2c11a7..5b5943f 100644
--- a/src/hooks/useStyles.ts
+++ b/src/hooks/useStyles.ts
@@ -1,14 +1,15 @@
-import { useStylesContext } from "../context/StylesContext";
+import { useStylesInternal } from "./internal/useStylesInternal";
 
 /**
  * External custom hook for managing styles.
  */
 export const useStyles = () => {
 	// handles styles
-	const { styles, setStyles } = useStylesContext();
+	const { styles, setStyles, updateStyles } = useStylesInternal();
 
 	return {
 		styles,
-		setStyles
+		setStyles,
+		updateStyles
 	};
 };
diff --git a/src/hooks/useToasts.ts b/src/hooks/useToasts.ts
index d4a4048..0820508 100644
--- a/src/hooks/useToasts.ts
+++ b/src/hooks/useToasts.ts
@@ -1,11 +1,11 @@
-import { useToastInternal } from "./internal/useToastsInternal";
+import { useToastsInternal } from "./internal/useToastsInternal";
 
 /**
  * External custom hook for managing toasts.
  */
-export const useToast = () => {
+export const useToasts = () => {
 	// handles toasts
-	const { showToast, dismissToast, toasts, setToasts } = useToastInternal();
+	const { showToast, dismissToast, toasts, setToasts } = useToastsInternal();
 
 	return {
 		showToast,
diff --git a/src/index.tsx b/src/index.tsx
index bdca2ee..bbc4022 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -46,7 +46,7 @@ import { usePaths } from "./hooks/usePaths";
 import { useSettings } from "./hooks/useSettings";
 import { useStyles } from "./hooks/useStyles";
 import { useTextArea } from "./hooks/useTextArea";
-import { useToast } from "./hooks/useToasts";
+import { useToasts } from "./hooks/useToasts";
 import { useVoice } from "./hooks/useVoice";
 import { useChatHistory } from "./hooks/useChatHistory";
 
@@ -109,7 +109,7 @@ export {
 	useSettings,
 	useStyles,
 	useTextArea,
-	useToast,
+	useToasts,
 	useVoice,
 	useChatHistory
 }
diff --git a/src/services/ThemeService.ts b/src/services/ThemeService.ts
index b293b9c..a29679f 100644
--- a/src/services/ThemeService.ts
+++ b/src/services/ThemeService.ts
@@ -2,7 +2,7 @@ import { Settings } from "../types/Settings";
 import { Styles } from "../types/Styles";
 import { Theme } from "../types/Theme";
 import { ThemeCacheData } from "../types/internal/ThemeCacheData";
-import { viteConfig } from "../../viteconfig";
+import { viteConfig } from "../viteconfig";
 
 // The configuration values like DEFAULT_URL, DEFAULT_EXPIRATION, and CACHE_KEY_PREFIX
 // were previously accessed using `import.meta.env` directly. To centralize and 
@@ -64,8 +64,6 @@ export const setCachedTheme = (id: string, version: string, settings: Settings,
 		cacheDate: currentTimeInSeconds
 	};
 
-	console.log(themeCacheData);
-
 	localStorage.setItem(`${CACHE_KEY_PREFIX}_${id}_${version}`, JSON.stringify(themeCacheData));
 }
 
diff --git a/src/utils/configParser.ts b/src/utils/configParser.ts
index cbf6631..3688c22 100644
--- a/src/utils/configParser.ts
+++ b/src/utils/configParser.ts
@@ -77,7 +77,7 @@ export const parseConfig = async (botId: string, providedSettings: Settings | un
  * @param preferredConfig config provided by the user to the bot
  * @param baseConfig the base config that the provided config will overwrite
  */
-const getCombinedConfig = (preferredConfig: Settings | Styles, baseConfig: Settings |
+export const getCombinedConfig = (preferredConfig: Settings | Styles, baseConfig: Settings |
 	Styles): Settings | Styles => {
 
 	const stack: Array<{ source: object, target: object }> = [{ source: preferredConfig, target: baseConfig }];
@@ -118,7 +118,7 @@ const getCombinedConfig = (preferredConfig: Settings | Styles, baseConfig: Setti
  * @param obj object to clone
  */
 /* eslint-disable @typescript-eslint/no-explicit-any */
-const deepClone = (obj: { [key: string]: any }): { [key: string]: any } => {
+export const deepClone = (obj: { [key: string]: any }): { [key: string]: any } => {
 	if (obj === null || typeof obj !== "object") {
 		return obj;
 	}
diff --git a/src/viteconfig.ts b/src/viteconfig.ts
new file mode 100644
index 0000000..54de4b4
--- /dev/null
+++ b/src/viteconfig.ts
@@ -0,0 +1,5 @@
+export const viteConfig = {
+	DEFAULT_URL: import.meta.env?.VITE_THEME_BASE_CDN_URL,
+	DEFAULT_EXPIRATION: import.meta.env?.VITE_THEME_DEFAULT_CACHE_EXPIRATION,
+	CACHE_KEY_PREFIX: import.meta.env?.VITE_THEME_CACHE_KEY_PREFIX,
+};
\ No newline at end of file
diff --git a/viteconfig.ts b/viteconfig.ts
deleted file mode 100644
index dee181b..0000000
--- a/viteconfig.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export const viteConfig = {
-    DEFAULT_URL: import.meta.env?.VITE_THEME_BASE_CDN_URL,
-    DEFAULT_EXPIRATION: import.meta.env?.VITE_THEME_DEFAULT_CACHE_EXPIRATION,
-    CACHE_KEY_PREFIX: import.meta.env?.VITE_THEME_CACHE_KEY_PREFIX,
-  };
\ No newline at end of file


Test patch:
diff --git a/__tests__/components/ChatBotBody/ToastPrompt.test.tsx b/__tests__/components/ChatBotBody/ToastPrompt.test.tsx
index 49c868b..b848f24 100644
--- a/__tests__/components/ChatBotBody/ToastPrompt.test.tsx
+++ b/__tests__/components/ChatBotBody/ToastPrompt.test.tsx
@@ -6,7 +6,7 @@ import "@testing-library/jest-dom/jest-globals";
 import ToastPrompt from "../../../src/components/ChatBotBody/ToastPrompt/ToastPrompt";
 import { useSettingsContext } from "../../../src/context/SettingsContext";
 import { useStylesContext } from "../../../src/context/StylesContext";
-import { useToastInternal } from "../../../src/hooks/internal/useToastsInternal";
+import { useToastsInternal } from "../../../src/hooks/internal/useToastsInternal";
 
 // Mock the contexts
 jest.mock("../../../src/context/SettingsContext");
@@ -47,7 +47,7 @@ describe("ToastPrompt Component", () => {
 			},
 		});
 
-		(useToastInternal as jest.Mock).mockReturnValue({
+		(useToastsInternal as jest.Mock).mockReturnValue({
 			dismissToast: dismissToastMock,
 		});
 	});
@@ -183,7 +183,7 @@ describe("ToastPrompt Component", () => {
     
 		// Mock the dismissToast function
 		const dismissToastMock = jest.fn();
-		(useToastInternal as jest.Mock).mockReturnValue({
+		(useToastsInternal as jest.Mock).mockReturnValue({
 			dismissToast: dismissToastMock,
 		});
     
@@ -215,7 +215,7 @@ describe("ToastPrompt Component", () => {
 
 	it("does not dismiss toast on click when dismissOnClick is false", () => {
 		const dismissToastMock = jest.fn();
-		(useToastInternal as jest.Mock).mockReturnValue({
+		(useToastsInternal as jest.Mock).mockReturnValue({
 			dismissToast: dismissToastMock,
 		});
 
diff --git a/__tests__/services/AudioService.test.ts b/__tests__/services/AudioService.test.ts
index 509736f..f36bc5f 100644
--- a/__tests__/services/AudioService.test.ts
+++ b/__tests__/services/AudioService.test.ts
@@ -1,5 +1,7 @@
 import React from "react";
 
+import { expect } from "@jest/globals";
+
 import { processAudio } from "../../src/services/AudioService";
 import { generateSecureUUID } from "../../src/utils/idGenerator";
 import { Settings } from "../../src/types/Settings";
diff --git a/__tests__/services/BlockService/BlockService.test.tsx b/__tests__/services/BlockService/BlockService.test.tsx
index 4f488ff..b978449 100644
--- a/__tests__/services/BlockService/BlockService.test.tsx
+++ b/__tests__/services/BlockService/BlockService.test.tsx
@@ -1,4 +1,7 @@
 import React from 'react'
+
+import { expect } from "@jest/globals";
+
 import {
 	preProcessBlock,
 	postProcessBlock,
diff --git a/__tests__/services/BlockService/CheckboxProcessor.test.tsx b/__tests__/services/BlockService/CheckboxProcessor.test.tsx
index 07774de..db52fee 100644
--- a/__tests__/services/BlockService/CheckboxProcessor.test.tsx
+++ b/__tests__/services/BlockService/CheckboxProcessor.test.tsx
@@ -1,3 +1,5 @@
+import { expect } from "@jest/globals";
+
 import { processCheckboxes } from "../../../src/services/BlockService/CheckboxProcessor";
 import { Block } from "../../../src/types/Block";
 import { Flow } from "../../../src/types/Flow";
diff --git a/__tests__/services/BlockService/IsSensitiveProcessor.test.ts b/__tests__/services/BlockService/IsSensitiveProcessor.test.ts
index 4ba85ac..956b3f2 100644
--- a/__tests__/services/BlockService/IsSensitiveProcessor.test.ts
+++ b/__tests__/services/BlockService/IsSensitiveProcessor.test.ts
@@ -1,3 +1,5 @@
+import { expect } from "@jest/globals";
+
 import { processIsSensitive } from "../../../src/services/BlockService/IsSensitiveProcessor";
 import { Block } from "../../../src/types/Block";
 import { Params } from "../../../src/types/Params";
diff --git a/__tests__/services/BlockService/OptionProcessor.test.tsx b/__tests__/services/BlockService/OptionProcessor.test.tsx
index c95f550..e933e16 100644
--- a/__tests__/services/BlockService/OptionProcessor.test.tsx
+++ b/__tests__/services/BlockService/OptionProcessor.test.tsx
@@ -1,4 +1,5 @@
 import React from "react";
+
 import { expect } from "@jest/globals";
 
 import { processOptions } from "../../../src/services/BlockService/OptionProcessor";
diff --git a/__tests__/services/FunctionProcessor.test.ts b/__tests__/services/FunctionProcessor.test.ts
index 586f6e0..5446d62 100644
--- a/__tests__/services/FunctionProcessor.test.ts
+++ b/__tests__/services/FunctionProcessor.test.ts
@@ -1,3 +1,5 @@
+import { expect } from "@jest/globals";
+
 import { processFunction } from "../../src/services/BlockService/FunctionProcessor";
 import { Block } from "../../src/types/Block";
 import { Params } from "../../src/types/Params";
diff --git a/__tests__/services/ThemeService.test.ts b/__tests__/services/ThemeService.test.ts
index 7a3fddc..857ed0d 100644
--- a/__tests__/services/ThemeService.test.ts
+++ b/__tests__/services/ThemeService.test.ts
@@ -1,13 +1,15 @@
+import { expect } from "@jest/globals";
+
 // Import the functions to be tested and types needed for the theme data
 import {
   getCachedTheme,
   setCachedTheme,
 } from "../../src/services/ThemeService";
 import { ThemeCacheData } from "../../src/types/internal/ThemeCacheData";
-import { viteConfig } from "../../viteconfig";
+import { viteConfig } from "../../src/viteconfig";
 
 // Mock the vite import so that the environment variables can be controlled in tests
-jest.mock("../../viteconfig", () => ({
+jest.mock("../../src/viteconfig", () => ({
   viteConfig: {
     DEFAULT_URL: "http://localhost:mock",
     DEFAULT_EXPIRATION: "60",
diff --git a/__tests__/services/TransitionProcessor.test.ts b/__tests__/services/TransitionProcessor.test.ts
index a65ca3a..0ed7029 100644
--- a/__tests__/services/TransitionProcessor.test.ts
+++ b/__tests__/services/TransitionProcessor.test.ts
@@ -1,3 +1,5 @@
+import { expect } from "@jest/globals";
+
 // Importing necessary functions and types
 import { processTransition } from "../../src/services/BlockService/TransitionProcessor"; 
 import { postProcessBlock } from "../../src/services/BlockService/BlockService"; 
diff --git a/__tests__/utils/mediaFileParser.test.ts b/__tests__/utils/mediaFileParser.test.ts
new file mode 100644
index 0000000..9e9e7be
--- /dev/null
+++ b/__tests__/utils/mediaFileParser.test.ts
@@ -0,0 +1,35 @@
+import { getMediaFileDetails } from '../../src/utils/mediaFileParser'
+
+describe('mediaFileParser', () => {
+	beforeEach(() => {
+		// Reset all mocks before each test
+		jest.clearAllMocks()
+	})
+
+	it('should return fileType and fileUrl', async () => {
+		const mockFile = new File([], 'file.jpg', { type: 'image/jpg' }) as File
+
+		const fileDetails = await getMediaFileDetails(mockFile)
+
+		expect(fileDetails.fileType).toBe('image')
+		expect(fileDetails.fileUrl).toContain('data:' + mockFile.type)
+	})
+
+	it('should return null when passed file is not a media file', async () => {
+		const mockFile = new File([], 'file.txt', { type: 'text/plain' }) as File
+
+		const fileDetails = await getMediaFileDetails(mockFile)
+
+		expect(fileDetails.fileType).toBe(null)
+		expect(fileDetails.fileUrl).toBe(null)
+	})
+
+	it('should return null when invalid file passed', async () => {
+		const mockFile = new File([], 'file.mp4') as File
+
+		const fileDetails = await getMediaFileDetails(mockFile)
+
+		expect(fileDetails.fileType).toBe(null)
+		expect(fileDetails.fileUrl).toBe(null)
+	})
+})