Old content
"; + const userRequest = JSON.stringify({ + description: "Try to modify an element with a bad selector", + modifications: [ + { + action: "replace", + content: "New Content
", + selector: "#;3s92hn", + }, + ], + }); + + const result = await modifyHtml(html, userRequest); + expect(result).not.toContain("New content
"); + expect(result).toContain("Old content
"); + }); + it("should be able to update timestamps", async () => { const html = `New Content
", + }; + const element = doc.createElement("div"); + doc.body.appendChild(element); + element.innerHTML = "Old Content
"; + await applyModification(element, modification, doc); + + expect(element.innerHTML).toBe("New Content
"); + }); + + it("should unapply replaced content correctly", async () => { + const modification: Modification = { + action: "replace", + content: "New Content
", + }; + const element = doc.createElement("div"); + doc.body.appendChild(element); + element.innerHTML = "Old Content
"; + const modifications = await applyModification(element, modification, doc); + modifications.unapply(); + + expect(element.innerHTML).toBe("Old Content
"); + }); + + it("should replace all content correctly", async () => { + const modification: Modification = { + action: "replaceAll", + content: "/old/new/", + }; + + const element = doc.createElement("div"); + doc.body.appendChild(element); + element.innerHTML = "Old Content
"; + await applyModification(element, modification, doc); + + expect(element.innerHTML).toBe("New Content
"); + }); + + it("should unapply replace all correctly", async () => { + const modification: Modification = { + action: "replaceAll", + content: "/old/new/", + }; + + const element = doc.createElement("div"); + doc.body.appendChild(element); + element.innerHTML = "Old Content
"; + const modifications = await applyModification(element, modification, doc); + modifications.unapply(); + + expect(element.innerHTML).toBe("Old Content
"); + }); + + it("should preserve capitals in replacement", async () => { + const modification: Modification = { + action: "replaceAll", + content: "/old/new/", + }; + + const element = doc.createElement("div"); + doc.body.appendChild(element); + element.innerHTML = "Old Content is old
"; + await applyModification(element, modification, doc); + + expect(element.innerHTML).toBe("New Content is new
"); + }); + + it("should preserve plurals in replacement", async () => { + const modification: Modification = { + action: "replaceAll", + content: "/train/brain/", + }; + + const element = doc.createElement("div"); + doc.body.appendChild(element); + element.innerHTML = "Trains are great! I love my train.
"; + await applyModification(element, modification, doc); + + expect(element.innerHTML).toBe( + "Brains are great! I love my brain.
", + ); + }); + + it("should only replace whole words", async () => { + const modification: Modification = { + action: "replaceAll", + content: "/train/brain/", + }; + + const element = doc.createElement("div"); + doc.body.appendChild(element); + element.innerHTML = + "I was in training about trains, but it was a strain to train.
"; + await applyModification(element, modification, doc); + + expect(element.innerHTML).toBe( + "I was in training about brains, but it was a strain to brain.
", + ); + }); + + it("should handle more complicated HTML", async () => { + const modification: Modification = { + action: "replaceAll", + content: "/train/brain/", + }; + + const element = doc.createElement("div"); + doc.body.appendChild(element); + element.innerHTML = + 'Trains are great! A picture of a train
Trains are great! I love my train.
'; + await applyModification(element, modification, doc); + + expect(element.innerHTML).toBe( + 'Brains are great! A picture of a brain
Brains are great! I love my brain.
', + ); + }); + + it("should unapply replaceall properly on more complicated HTML", async () => { + const modification: Modification = { + action: "replaceAll", + content: "/train/brain/", + }; + + const element = doc.createElement("div"); + doc.body.appendChild(element); + element.innerHTML = + 'Trains are great! A picture of a train
Trains are great! I love my train.
'; + const modifications = await applyModification(element, modification, doc); + modifications.unapply(); + + expect(element.innerHTML).toBe( + 'Trains are great! A picture of a train
Trains are great! I love my train.
', + ); + }); + + it("should work with multiple text nodes", async () => { + const modification: Modification = { + action: "replaceAll", + content: "/train/brain/", + }; + + const element = doc.createElement("div"); + doc.body.appendChild(element); + const t1 = doc.createTextNode("Trains node 1 "); + const t2 = doc.createTextNode("Trains node 2 "); + const t3 = doc.createTextNode("Trains node 3"); + element.appendChild(t1); + element.appendChild(t2); + element.appendChild(t3); + + await applyModification(element, modification, doc); + + expect(element.innerHTML).toBe( + "Brains node 1 Brains node 2 Brains node 3", + ); + }); + + it("should append content correctly", async () => { + const modification: Modification = { + action: "append", + content: "New Content
", + }; + const element = doc.createElement("div"); + doc.body.appendChild(element); + const inner = doc.createElement("div"); + inner.innerHTML = "Initial Content
"; + element.appendChild(inner); + await applyModification(inner, modification, doc); + + expect(element.innerHTML).toBe( + "Initial Content
New Content
New Content
", + }; + const element = doc.createElement("div"); + doc.body.appendChild(element); + const inner = doc.createElement("div"); + inner.innerHTML = "
Initial Content
"; + element.appendChild(inner); + const modifications = await applyModification(inner, modification, doc); + modifications.unapply(); + + expect(element.innerHTML).toBe("Initial Content
New Content
", + }; + const element = doc.createElement("div"); + doc.body.appendChild(element); + const inner = doc.createElement("div"); + inner.innerHTML = "Initial Content
"; + element.appendChild(inner); + await applyModification(inner, modification, doc); + + expect(element.innerHTML).toBe( + "New Content
Initial Content
New Content
", + }; + const element = doc.createElement("div"); + doc.body.appendChild(element); + const inner = doc.createElement("div"); + inner.innerHTML = "Initial Content
"; + element.appendChild(inner); + const modifications = await applyModification(inner, modification, doc); + modifications.unapply(); + + expect(element.innerHTML).toBe("Initial Content
Initial Content
Inner child 1
Inner child 3
Inner child 1
Inner child 2
Inner child 3
Content
"; + await applyModification(element, modification, doc); + + expect(element.style.border).toBe("2px solid green"); + }); + + it("should create and display a toast correctly", async () => { + const modification: Modification = { + action: "toast", + toastMessage: "Test Notification", + duration: 100, + }; + createToast(modification.toastMessage ?? "", doc, modification.duration); + + // Simulate checking if the toast exists + const toastElement = doc.querySelector(".bg-blue-500"); // Assuming '.bg-blue-500' is the class for the toast + expect(toastElement?.textContent).toBe("Test Notification"); + + // Simulate waiting for the toast to be removed + await new Promise((resolve) => setTimeout(resolve, 200)); + + expect(doc.querySelector(".bg-blue-500")).toBeNull(); + }); + + it("should add a component correctly", async () => { + const modification: Modification = { + action: "addComponent", + componentHtml: "Component Content", + }; + const element = doc.createElement("div"); + doc.body.appendChild(element); + const inner = doc.createElement("p"); + inner.innerHTML = "Initial Content"; + element.appendChild(inner); + await applyModification(inner, modification, doc); + + expect(element.innerHTML).toBe( + "Initial ContentComponent Content
", + ); + }); + + it("should unapply the add component correctly", async () => { + const modification: Modification = { + action: "addComponent", + componentHtml: "Component Content", + }; + const element = doc.createElement("div"); + doc.body.appendChild(element); + const inner = doc.createElement("p"); + inner.innerHTML = "Initial Content"; + element.appendChild(inner); + const modifications = await applyModification(inner, modification, doc); + modifications.unapply(); + + expect(element.innerHTML).toBe("Initial Content
"); + }); + + it("should handle unknowns correctly", async () => { + const modification: Modification = { + action: "unknown", + componentHtml: "Component Content", + }; + const element = doc.createElement("div"); + doc.body.appendChild(element); + element.innerHTML = "Initial Content
"; + await applyModification(element, modification, doc); + + expect(element.innerHTML).toContain("Initial Content
"); + }); + }); + + describe("createToast", () => { + it("should create and remove a toast correctly", async () => { + const message = "Test Message"; + createToast(message, doc); + + // Check if the toast exists + const toastElement = doc.querySelector(".bg-blue-500"); + expect(toastElement?.textContent).toBe(message); + + // Wait for the timeout before removing the toast + await new Promise((resolve) => setTimeout(resolve, 3100)); // Adjusted slightly above the 3000ms to account for any delays + + expect(doc.querySelector(".bg-blue-500")).toBeNull(); + }); + }); + + describe("generateModifications", () => { + it("should handle empty selectors gracefully", async () => { + const request: ModificationRequest = { + modifications: [ + { + selector: "", + action: "replace", + content: "New Content
", + }, + ], + description: "", + }; + + await generateModifications(request, doc); + }); + }); +}); diff --git a/packages/reactor/tests/utils.test.ts b/packages/reactor/tests/utils.test.ts deleted file mode 100644 index 29820c18..00000000 --- a/packages/reactor/tests/utils.test.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { beforeEach, describe, expect, it } from "vitest"; -import type { Modification, ModificationRequest } from "../interfaces"; -// utils.test.ts -import { applyModification, createToast } from "../utils"; -import { generateModifications } from "../utils"; - -describe("Utils", () => { - let doc: Document; - - // Vitest beforeEach function for setup - beforeEach(() => { - doc = document.implementation.createHTMLDocument("Test Document"); - }); - - describe("applyModification", () => { - it("should replace content correctly", async () => { - const modification: Modification = { - action: "replace", - content: "New Content
", - }; - const element = doc.createElement("div"); - element.innerHTML = "Old Content
"; - await applyModification(element, modification, doc); - - expect(element.innerHTML).toBe("New Content
"); - }); - - it("should append content correctly", async () => { - const modification: Modification = { - action: "append", - content: "New Content
", - }; - const element = doc.createElement("div"); - element.innerHTML = "Initial Content
"; - await applyModification(element, modification, doc); - - expect(element.innerHTML).toBe( - "Initial Content
New Content
", - ); - }); - - it("should prepend content correctly", async () => { - const modification: Modification = { - action: "prepend", - content: "New Content
", - }; - const element = doc.createElement("div"); - element.innerHTML = "Initial Content
"; - await applyModification(element, modification, doc); - - expect(element.innerHTML).toBe( - "New Content
Initial Content
", - ); - }); - - it("should remove the element correctly", async () => { - const modification: Modification = { - action: "remove", - }; - const element = doc.createElement("div"); - element.innerHTML = "Content
"; - await applyModification(element, modification, doc); - - expect(element.parentElement).toBeNull(); - }); - - it("should ignore invalid selectors", async () => { - const modification: Modification = { - action: "replace", - content: "New Content
", - selector: "#;3s92hn", - }; - const element = doc.createElement("div"); - element.innerHTML = "Old Content
"; - await applyModification(element, modification, doc); - - expect(element.parentElement).toBeNull(); - }); - - it("should swap image source correctly", async () => { - const modification: Modification = { - action: "swapImage", - imageUrl: "new-image-url.jpg", - }; - const element = doc.createElement("img"); - element.src = "old-image-url.jpg"; - await applyModification(element, modification, doc); - - expect(element.src).toBe("new-image-url.jpg"); - }); - - it("should highlight element correctly", async () => { - const modification: Modification = { - action: "highlight", - highlightStyle: "2px solid green", - }; - const element = doc.createElement("div"); - element.innerHTML = "Content
"; - await applyModification(element, modification, doc); - - expect(element.style.border).toBe("2px solid green"); - }); - - it("should create and display a toast correctly", async () => { - const modification: Modification = { - action: "toast", - toastMessage: "Test Notification", - duration: 100, - }; - createToast(modification.toastMessage ?? "", doc, modification.duration); - - // Simulate checking if the toast exists - const toastElement = doc.querySelector(".bg-blue-500"); // Assuming '.bg-blue-500' is the class for the toast - expect(toastElement?.textContent).toBe("Test Notification"); - - // Simulate waiting for the toast to be removed - await new Promise((resolve) => setTimeout(resolve, 200)); - - expect(doc.querySelector(".bg-blue-500")).toBeNull(); - }); - - it("should add a component correctly", async () => { - const modification: Modification = { - action: "addComponent", - componentHtml: "Component Content", - }; - const element = doc.createElement("div"); - element.innerHTML = "Initial Content
"; - await applyModification(element, modification, doc); - - expect(element.innerHTML).toContain("Component Content"); - }); - - it("should handle unknowns correctly", async () => { - const modification: Modification = { - action: "unknown", - componentHtml: "Component Content", - }; - const element = doc.createElement("div"); - element.innerHTML = "Initial Content
"; - await applyModification(element, modification, doc); - - expect(element.innerHTML).toContain("Initial Content
"); - }); - }); - - describe("createToast", () => { - it("should create and remove a toast correctly", async () => { - const message = "Test Message"; - createToast(message, doc); - - // Check if the toast exists - const toastElement = doc.querySelector(".bg-blue-500"); - expect(toastElement?.textContent).toBe(message); - - // Wait for the timeout before removing the toast - await new Promise((resolve) => setTimeout(resolve, 3100)); // Adjusted slightly above the 3000ms to account for any delays - - expect(doc.querySelector(".bg-blue-500")).toBeNull(); - }); - }); - - describe("generateModifications", () => { - it("should handle empty selectors gracefully", async () => { - const request: ModificationRequest = { - modifications: [ - { - selector: "", - action: "replace", - content: "New Content
", - }, - ], - description: "", - }; - - await generateModifications(request, doc); - }); - }); -}); diff --git a/packages/reactor/utils.ts b/packages/reactor/utils.ts index 51742294..4cf95272 100644 --- a/packages/reactor/utils.ts +++ b/packages/reactor/utils.ts @@ -1,4 +1,11 @@ -import type { Modification, ModificationRequest } from "./interfaces"; +// utils.ts +const cssSelector = require("css-selector-generator"); +import type { + AppliedModifications, + Modification, + ModificationRequest, + TimeStampReference, +} from "./interfaces"; export function parseRequest(userRequest: string): ModificationRequest { try { @@ -9,263 +16,3 @@ export function parseRequest(userRequest: string): ModificationRequest { throw new Error("Invalid user request format"); } } - -function calculateNewDate( - originalDay: string, - originalMonth: string, - recordedAt: string, - currentTime: string, -): { newDay: string, newMonth: string } { - const months = [ - "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" - ]; - const recordedDate = new Date(recordedAt); - const currentDate = new Date(currentTime); - const differenceInDays = Math.ceil( - Math.abs( - (currentDate.getTime() - recordedDate.getTime()) / (1000 * 3600 * 24), - ), - ); - - const originalDate = new Date(recordedDate); - originalDate.setDate(Number.parseInt(originalDay, 10)); - - const newDate = new Date(originalDate); - newDate.setDate(originalDate.getDate() + differenceInDays); - - const newDay = String(newDate.getDate()).padStart(2, "0"); - const newMonth = months[newDate.getMonth()] || originalMonth; - - return { newDay, newMonth }; -} - -export async function generateModifications( - request: ModificationRequest, - doc: Document, -): Promise