From 8dc419d8ca5155aa5de02b66d2d6a6f55e25f81a Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 13 Sep 2025 11:16:32 +0000
Subject: [PATCH 1/4] Initial plan
From 2383e282d0049100e3cb6d69dfae04b0fdbb6d91 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 13 Sep 2025 11:23:07 +0000
Subject: [PATCH 2/4] feat(Campaign): add createContext template method for
extensibility
Co-authored-by: timowestnosto <13622115+timowestnosto@users.noreply.github.com>
---
src/components/Campaign/Campaign.ts | 26 ++++++++++++++++-
test/components/Campaign.spec.tsx | 44 +++++++++++++++++++++++++++++
2 files changed, 69 insertions(+), 1 deletion(-)
diff --git a/src/components/Campaign/Campaign.ts b/src/components/Campaign/Campaign.ts
index f9847770..b655bdbd 100644
--- a/src/components/Campaign/Campaign.ts
+++ b/src/components/Campaign/Campaign.ts
@@ -67,6 +67,30 @@ export class Campaign extends NostoElement {
async load() {
await loadCampaign(this)
}
+
+ /**
+ * Extension point: override to enrich or modify the template context.
+ * This method is called during campaign rendering to create the context object
+ * that will be passed to the template. Subclasses can override this method
+ * to add custom properties, feature flags, or transform the data.
+ *
+ * @param raw - The raw JSON result from the Nosto API
+ * @returns The context object to be used in template rendering
+ *
+ * @example
+ * ```typescript
+ * class CustomCampaign extends Campaign {
+ * createContext(raw) {
+ * const context = super.createContext(raw);
+ * return { ...context, customProperty: 'value' };
+ * }
+ * }
+ * ```
+ */
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ createContext(raw: JSONResult): any {
+ return getContext(raw)
+ }
}
export async function loadCampaign(element: Campaign) {
@@ -85,7 +109,7 @@ export async function loadCampaign(element: Campaign) {
if (rec) {
if (useTemplate) {
const template = getTemplate(element)
- compile(element, template, getContext(rec as JSONResult))
+ compile(element, template, element.createContext(rec as JSONResult))
api.attributeProductClicksInCampaign(element, rec as JSONResult)
} else {
await api.placements.injectCampaigns(
diff --git a/test/components/Campaign.spec.tsx b/test/components/Campaign.spec.tsx
index 5e4e70e2..328c1a1d 100644
--- a/test/components/Campaign.spec.tsx
+++ b/test/components/Campaign.spec.tsx
@@ -163,4 +163,48 @@ describe("Campaign", () => {
expect(mockObserver.observe).not.toHaveBeenCalled()
expect(mockBuilder.load).not.toHaveBeenCalled()
})
+
+ it("should allow subclasses to override createContext method", async () => {
+ // Create a custom campaign that extends the base Campaign
+ class CustomCampaign extends Campaign {
+ createContext(raw: any) {
+ const context = super.createContext(raw)
+ return { ...context, customProperty: "customValue", modified: true }
+ }
+ }
+
+ // Register the custom element
+ if (!customElements.get("custom-campaign")) {
+ customElements.define("custom-campaign", CustomCampaign)
+ }
+
+ const templateId = "custom-template"
+ const template = document.createElement("template")
+ template.id = templateId
+ template.innerHTML = `
+
{{ customProperty }}
+ {{ modified }}
+ {{ title }}
+ `
+ document.body.appendChild(template)
+
+ const { mockBuilder } = mockNostoRecs({
+ "custom-123": {
+ title: "Custom Campaign Test"
+ }
+ })
+
+ // Create custom element using JSX-like syntax
+ const customCampaign = document.createElement("custom-campaign") as Campaign
+ customCampaign.setAttribute("placement", "custom-123")
+ customCampaign.setAttribute("template", templateId)
+ document.body.appendChild(customCampaign)
+
+ await customCampaign.connectedCallback()
+
+ expect(customCampaign.innerHTML).toContain("customValue")
+ expect(customCampaign.innerHTML).toContain("true")
+ expect(customCampaign.innerHTML).toContain("Custom Campaign Test")
+ expect(mockBuilder.load).toHaveBeenCalledWith()
+ })
})
From c0f59a2940a884d70ebfbdb16624545d99b79c72 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 13 Sep 2025 11:32:43 +0000
Subject: [PATCH 3/4] fix: change createContext return type to object and use
JSX syntax in test
Co-authored-by: timowestnosto <13622115+timowestnosto@users.noreply.github.com>
---
src/components/Campaign/Campaign.ts | 3 +--
test/components/Campaign.spec.tsx | 9 ++++-----
2 files changed, 5 insertions(+), 7 deletions(-)
diff --git a/src/components/Campaign/Campaign.ts b/src/components/Campaign/Campaign.ts
index b655bdbd..5e3413de 100644
--- a/src/components/Campaign/Campaign.ts
+++ b/src/components/Campaign/Campaign.ts
@@ -87,8 +87,7 @@ export class Campaign extends NostoElement {
* }
* ```
*/
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- createContext(raw: JSONResult): any {
+ createContext(raw: JSONResult): object {
return getContext(raw)
}
}
diff --git a/test/components/Campaign.spec.tsx b/test/components/Campaign.spec.tsx
index 328c1a1d..a875b732 100644
--- a/test/components/Campaign.spec.tsx
+++ b/test/components/Campaign.spec.tsx
@@ -1,6 +1,7 @@
/** @jsx createElement */
import { describe, it, expect, vi, Mock } from "vitest"
import { Campaign } from "@/components/Campaign/Campaign"
+import { JSONResult } from "@nosto/nosto-js/client"
import { mockNostoRecs } from "../mockNostoRecs"
import { createElement } from "../utils/jsx"
@@ -167,7 +168,7 @@ describe("Campaign", () => {
it("should allow subclasses to override createContext method", async () => {
// Create a custom campaign that extends the base Campaign
class CustomCampaign extends Campaign {
- createContext(raw: any) {
+ createContext(raw: JSONResult) {
const context = super.createContext(raw)
return { ...context, customProperty: "customValue", modified: true }
}
@@ -194,10 +195,8 @@ describe("Campaign", () => {
}
})
- // Create custom element using JSX-like syntax
- const customCampaign = document.createElement("custom-campaign") as Campaign
- customCampaign.setAttribute("placement", "custom-123")
- customCampaign.setAttribute("template", templateId)
+ // Create custom element using JSX syntax
+ const customCampaign = () as Campaign
document.body.appendChild(customCampaign)
await customCampaign.connectedCallback()
From 3a7b2fcc8767f3628f2cfab714723c102d0f5e80 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 15 Sep 2025 10:24:00 +0000
Subject: [PATCH 4/4] feat(Campaign): make createContext async for better
extensibility
Co-authored-by: timowestnosto <13622115+timowestnosto@users.noreply.github.com>
---
src/components/Campaign/Campaign.ts | 8 ++++----
test/components/Campaign.spec.tsx | 4 ++--
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/src/components/Campaign/Campaign.ts b/src/components/Campaign/Campaign.ts
index 5e3413de..3d03c222 100644
--- a/src/components/Campaign/Campaign.ts
+++ b/src/components/Campaign/Campaign.ts
@@ -80,14 +80,14 @@ export class Campaign extends NostoElement {
* @example
* ```typescript
* class CustomCampaign extends Campaign {
- * createContext(raw) {
- * const context = super.createContext(raw);
+ * async createContext(raw) {
+ * const context = await super.createContext(raw);
* return { ...context, customProperty: 'value' };
* }
* }
* ```
*/
- createContext(raw: JSONResult): object {
+ async createContext(raw: JSONResult): Promise