From f3e5b9dac358b48730bc73956a052cafd5522f9a Mon Sep 17 00:00:00 2001 From: mosherBT Date: Tue, 21 Jul 2026 10:46:33 -0400 Subject: [PATCH 1/2] splitTest: Rename none/all to test/production --- lib/addons/abTestAssignment.md | 12 +++---- lib/addons/abTestAssignment.test.ts | 54 ++++++++++++++--------------- lib/addons/abTestAssignment.ts | 6 ++-- 3 files changed, 36 insertions(+), 36 deletions(-) diff --git a/lib/addons/abTestAssignment.md b/lib/addons/abTestAssignment.md index d79adbd..0a32050 100644 --- a/lib/addons/abTestAssignment.md +++ b/lib/addons/abTestAssignment.md @@ -11,8 +11,8 @@ import { setupAB } from "@optable/web-sdk/lib/addons/abTestAssignment"; const ab = setupAB({ variants: [ - { id: "all" }, // treatment — gets remaining traffic (95%) - { id: "none", trafficPercentage: 5 }, // control — 5% + { id: "production" }, // treatment — gets remaining traffic (95%) + { id: "test", trafficPercentage: 5 }, // control — 5% ], }); @@ -31,7 +31,7 @@ import { setupAB } from "@optable/web-sdk/lib/addons/abTestAssignment"; import OptablePrebidAnalytics from "@optable/web-sdk/lib/addons/prebid/analytics"; const ab = setupAB({ - variants: [{ id: "all" }, { id: "none", trafficPercentage: 5 }], + variants: [{ id: "production" }, { id: "test", trafficPercentage: 5 }], pbjs, // hooks registered automatically }); @@ -42,7 +42,7 @@ analytics.hookIntoPrebid(); If `pbjs` is not yet available at setup time, call `ab.setHooks(pbjs)` manually once it is: ```js -const ab = setupAB({ variants: [{ id: "all" }, { id: "none", trafficPercentage: 5 }] }); +const ab = setupAB({ variants: [{ id: "production" }, { id: "test", trafficPercentage: 5 }] }); window.pbjs.que.push(() => ab.setHooks(window.pbjs)); ``` @@ -85,8 +85,8 @@ const ab = setupAB({ | ------------- | ----------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | `variants` | `ABTestVariant[]` | required | List of variants. Each has an `id` and an optional `trafficPercentage`. Variants without `trafficPercentage` share the remaining traffic equally. | | `storageKey` | `string` | `"OPTABLE_SPLIT_TEST"` | `localStorage` key used to persist the assignment across sessions. | -| `controlId` | `string` | `"none"` | The variant `id` considered the control group. Used to resolve `isControl` and the `optableControlGroup` flag override. | -| `treatmentId` | `string` | `"all"` | The variant `id` considered the treatment group. Used to resolve `isControl` and the `optableControlGroup` flag override. | +| `controlId` | `string` | `"test"` | The variant `id` considered the control group. Used to resolve `isControl` and the `optableControlGroup` flag override. | +| `treatmentId` | `string` | `"production"` | The variant `id` considered the treatment group. Used to resolve `isControl` and the `optableControlGroup` flag override. | | `sdk` | `OptableSDK` | — | When provided, uses `sdk.targetingClearCache()` for precise control-group cache clearing instead of a key-prefix scan. | | `pbjs` | `object` | — | When provided, bid-stamping hooks are registered on `pbjs` automatically at setup time. | diff --git a/lib/addons/abTestAssignment.test.ts b/lib/addons/abTestAssignment.test.ts index bd272db..dd6d0f2 100644 --- a/lib/addons/abTestAssignment.test.ts +++ b/lib/addons/abTestAssignment.test.ts @@ -18,15 +18,15 @@ afterEach(() => { describe("setupAB - traffic assignment", () => { it("assigns the treatment variant when random bucket falls in its range", () => { jest.spyOn(Math, "random").mockReturnValue(0.0); - const result = setupAB({ variants: [{ id: "all" }, { id: "none", trafficPercentage: 5 }] }); - expect(result.variant.id).toBe("all"); + const result = setupAB({ variants: [{ id: "production" }, { id: "test", trafficPercentage: 5 }] }); + expect(result.variant.id).toBe("production"); expect(result.isControl).toBe(false); }); it("assigns the control variant when random bucket falls in its range", () => { jest.spyOn(Math, "random").mockReturnValue(0.97); - const result = setupAB({ variants: [{ id: "all" }, { id: "none", trafficPercentage: 5 }] }); - expect(result.variant.id).toBe("none"); + const result = setupAB({ variants: [{ id: "production" }, { id: "test", trafficPercentage: 5 }] }); + expect(result.variant.id).toBe("test"); expect(result.isControl).toBe(true); }); @@ -41,27 +41,27 @@ describe("setupAB - traffic assignment", () => { describe("setupAB - localStorage stickiness", () => { it("persists the assignment to localStorage", () => { - setupAB({ variants: [{ id: "all" }, { id: "none", trafficPercentage: 5 }] }); + setupAB({ variants: [{ id: "production" }, { id: "test", trafficPercentage: 5 }] }); const stored = JSON.parse(localStorage.getItem(STORAGE_KEY)!); - expect(stored.id).toBe("all"); + expect(stored.id).toBe("production"); }); it("returns the cached assignment on subsequent calls", () => { - localStorage.setItem(STORAGE_KEY, JSON.stringify({ id: "none", trafficPercentage: 5 })); - const result = setupAB({ variants: [{ id: "all" }, { id: "none", trafficPercentage: 5 }] }); - expect(result.variant.id).toBe("none"); + localStorage.setItem(STORAGE_KEY, JSON.stringify({ id: "test", trafficPercentage: 5 })); + const result = setupAB({ variants: [{ id: "production" }, { id: "test", trafficPercentage: 5 }] }); + expect(result.variant.id).toBe("test"); }); it("ignores a cached value whose id is not in the variants list", () => { localStorage.setItem(STORAGE_KEY, JSON.stringify({ id: "stale", trafficPercentage: 50 })); jest.spyOn(Math, "random").mockReturnValue(0.0); - const result = setupAB({ variants: [{ id: "all" }, { id: "none", trafficPercentage: 5 }] }); - expect(result.variant.id).toBe("all"); + const result = setupAB({ variants: [{ id: "production" }, { id: "test", trafficPercentage: 5 }] }); + expect(result.variant.id).toBe("production"); }); it("respects a custom storageKey", () => { const key = "MY_AB_TEST"; - setupAB({ variants: [{ id: "all" }, { id: "none", trafficPercentage: 5 }], storageKey: key }); + setupAB({ variants: [{ id: "production" }, { id: "test", trafficPercentage: 5 }], storageKey: key }); expect(localStorage.getItem(key)).not.toBeNull(); expect(localStorage.getItem(STORAGE_KEY)).toBeNull(); }); @@ -71,16 +71,16 @@ describe("setupAB - override via flags", () => { it("forces control when optableControlGroup flag is '1' (sessionStorage)", () => { sessionStorage.setItem("optableControlGroup", "1"); resetFlags(); - const result = setupAB({ variants: [{ id: "all" }, { id: "none", trafficPercentage: 5 }] }); - expect(result.variant.id).toBe("none"); + const result = setupAB({ variants: [{ id: "production" }, { id: "test", trafficPercentage: 5 }] }); + expect(result.variant.id).toBe("test"); expect(result.isControl).toBe(true); }); it("forces treatment when optableControlGroup flag is '0' (sessionStorage)", () => { sessionStorage.setItem("optableControlGroup", "0"); resetFlags(); - const result = setupAB({ variants: [{ id: "all" }, { id: "none", trafficPercentage: 5 }] }); - expect(result.variant.id).toBe("all"); + const result = setupAB({ variants: [{ id: "production" }, { id: "test", trafficPercentage: 5 }] }); + expect(result.variant.id).toBe("production"); expect(result.isControl).toBe(false); }); }); @@ -105,7 +105,7 @@ describe("setupAB - control group cache clearing", () => { localStorage.setItem("OPTABLE_TARGETING_abc123", "stale"); localStorage.setItem("OPTABLE_TARGETING_def456", "stale"); jest.spyOn(Math, "random").mockReturnValue(0.97); // control bucket - setupAB({ variants: [{ id: "all" }, { id: "none", trafficPercentage: 5 }] }); + setupAB({ variants: [{ id: "production" }, { id: "test", trafficPercentage: 5 }] }); expect(localStorage.getItem("OPTABLE_RESOLVED")).toBeNull(); expect(localStorage.getItem("OPTABLE_TARGETING_abc123")).toBeNull(); expect(localStorage.getItem("OPTABLE_TARGETING_def456")).toBeNull(); @@ -115,7 +115,7 @@ describe("setupAB - control group cache clearing", () => { localStorage.setItem("OPTABLE_RESOLVED", "valid"); localStorage.setItem("OPTABLE_TARGETING_abc123", "valid"); jest.spyOn(Math, "random").mockReturnValue(0.0); // treatment bucket - setupAB({ variants: [{ id: "all" }, { id: "none", trafficPercentage: 5 }] }); + setupAB({ variants: [{ id: "production" }, { id: "test", trafficPercentage: 5 }] }); expect(localStorage.getItem("OPTABLE_RESOLVED")).toBe("valid"); expect(localStorage.getItem("OPTABLE_TARGETING_abc123")).toBe("valid"); }); @@ -123,7 +123,7 @@ describe("setupAB - control group cache clearing", () => { it("calls sdk.targetingClearCache() instead of prefix scan when sdk is provided", () => { const mockSdk = { targetingClearCache: jest.fn() }; jest.spyOn(Math, "random").mockReturnValue(0.97); // control bucket - setupAB({ variants: [{ id: "all" }, { id: "none", trafficPercentage: 5 }], sdk: mockSdk }); + setupAB({ variants: [{ id: "production" }, { id: "test", trafficPercentage: 5 }], sdk: mockSdk }); expect(mockSdk.targetingClearCache).toHaveBeenCalledTimes(1); }); @@ -131,39 +131,39 @@ describe("setupAB - control group cache clearing", () => { localStorage.setItem("OPTABLE_RESOLVED", "stale"); sessionStorage.setItem("optableControlGroup", "1"); resetFlags(); - setupAB({ variants: [{ id: "all" }, { id: "none", trafficPercentage: 5 }] }); + setupAB({ variants: [{ id: "production" }, { id: "test", trafficPercentage: 5 }] }); expect(localStorage.getItem("OPTABLE_RESOLVED")).toBeNull(); }); }); describe("setupAB - splitTestAssignment", () => { it("exposes the assigned variant id as a string", () => { - const result = setupAB({ variants: [{ id: "all" }, { id: "none", trafficPercentage: 5 }] }); + const result = setupAB({ variants: [{ id: "production" }, { id: "test", trafficPercentage: 5 }] }); expect(result.splitTestAssignment).toBe(result.variant.id); }); }); describe("setupAB - setHooks", () => { it("registers an onEvent handler for auctionEnd", () => { - const result = setupAB({ variants: [{ id: "all" }, { id: "none", trafficPercentage: 5 }] }); + const result = setupAB({ variants: [{ id: "production" }, { id: "test", trafficPercentage: 5 }] }); const pbjs = { getEvents: () => [], onEvent: jest.fn() }; result.setHooks(pbjs); expect(pbjs.onEvent).toHaveBeenCalledWith("auctionEnd", expect.any(Function)); }); it("replays past auctionEnd events from getEvents", () => { - const result = setupAB({ variants: [{ id: "all" }, { id: "none", trafficPercentage: 5 }] }); + const result = setupAB({ variants: [{ id: "production" }, { id: "test", trafficPercentage: 5 }] }); const bid: any = { bidId: "bid-1" }; const pbjs = { getEvents: () => [{ eventType: "auctionEnd", args: { bidderRequests: [{ bids: [bid] }] } }], onEvent: jest.fn(), }; result.setHooks(pbjs); - expect(bid.ortb2Imp.ext.optable.splitTestAssignment).toBe("all"); + expect(bid.ortb2Imp.ext.optable.splitTestAssignment).toBe("production"); }); it("stamps bids and does not overwrite an existing splitTestAssignment", () => { - const result = setupAB({ variants: [{ id: "all" }, { id: "none", trafficPercentage: 5 }] }); + const result = setupAB({ variants: [{ id: "production" }, { id: "test", trafficPercentage: 5 }] }); const bid: any = { bidId: "bid-1", ortb2Imp: { ext: { optable: { splitTestAssignment: "control" } } } }; const pbjs = { getEvents: () => [{ eventType: "auctionEnd", args: { bidderRequests: [{ bids: [bid] }] } }], @@ -173,9 +173,9 @@ describe("setupAB - setHooks", () => { expect(bid.ortb2Imp.ext.optable.splitTestAssignment).toBe("control"); }); - it("registers hooks automatically when pbjs is passed to setupAB", () => { + it("registers hooks automatically when pbjs is passed to setupAB", () => { const pbjs = { getEvents: () => [], onEvent: jest.fn() }; - setupAB({ variants: [{ id: "all" }, { id: "none", trafficPercentage: 5 }], pbjs }); + setupAB({ variants: [{ id: "production" }, { id: "test", trafficPercentage: 5 }], pbjs }); expect(pbjs.onEvent).toHaveBeenCalledWith("auctionEnd", expect.any(Function)); }); }); diff --git a/lib/addons/abTestAssignment.ts b/lib/addons/abTestAssignment.ts index ccbdd8e..6221440 100644 --- a/lib/addons/abTestAssignment.ts +++ b/lib/addons/abTestAssignment.ts @@ -12,9 +12,9 @@ export interface ABTestVariant { export interface SetupABConfig { variants: ABTestVariant[]; storageKey?: string; - // The variant id treated as "control" (Optable disabled). Defaults to 'none'. + // The variant id treated as "control" (Optable disabled). Defaults to 'test'. controlId?: string; - // The variant id treated as "treatment" (Optable enabled). Defaults to 'all'. + // The variant id treated as "treatment" (Optable enabled). Defaults to 'production'. treatmentId?: string; // An initialized SDK instance. When provided, targetingClearCache() is used // for precise cache clearing in the control group instead of a prefix scan. @@ -42,7 +42,7 @@ function fillTrafficPercentages(variants: ABTestVariant[]): ABTestConfig[] { } export function setupAB(config: SetupABConfig): ABTestSetupResult { - const { variants, storageKey = DEFAULT_STORAGE_KEY, controlId = "none", treatmentId = "all", sdk, pbjs } = config; + const { variants, storageKey = DEFAULT_STORAGE_KEY, controlId = "test", treatmentId = "production", sdk, pbjs } = config; // Process the provided variant config so that every variant has an explicit traffic percentage. // Variants without one share the remaining percentage equally. From bfd7d674c1730a78656a04d6f357e10835ca2d57 Mon Sep 17 00:00:00 2001 From: mosherBT Date: Tue, 21 Jul 2026 11:02:54 -0400 Subject: [PATCH 2/2] splitTest: apply prettier formatting --- lib/addons/abTestAssignment.md | 2 +- lib/addons/abTestAssignment.ts | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/addons/abTestAssignment.md b/lib/addons/abTestAssignment.md index 0a32050..fd33813 100644 --- a/lib/addons/abTestAssignment.md +++ b/lib/addons/abTestAssignment.md @@ -86,7 +86,7 @@ const ab = setupAB({ | `variants` | `ABTestVariant[]` | required | List of variants. Each has an `id` and an optional `trafficPercentage`. Variants without `trafficPercentage` share the remaining traffic equally. | | `storageKey` | `string` | `"OPTABLE_SPLIT_TEST"` | `localStorage` key used to persist the assignment across sessions. | | `controlId` | `string` | `"test"` | The variant `id` considered the control group. Used to resolve `isControl` and the `optableControlGroup` flag override. | -| `treatmentId` | `string` | `"production"` | The variant `id` considered the treatment group. Used to resolve `isControl` and the `optableControlGroup` flag override. | +| `treatmentId` | `string` | `"production"` | The variant `id` considered the treatment group. Used to resolve `isControl` and the `optableControlGroup` flag override. | | `sdk` | `OptableSDK` | — | When provided, uses `sdk.targetingClearCache()` for precise control-group cache clearing instead of a key-prefix scan. | | `pbjs` | `object` | — | When provided, bid-stamping hooks are registered on `pbjs` automatically at setup time. | diff --git a/lib/addons/abTestAssignment.ts b/lib/addons/abTestAssignment.ts index 6221440..8ec63c8 100644 --- a/lib/addons/abTestAssignment.ts +++ b/lib/addons/abTestAssignment.ts @@ -42,7 +42,14 @@ function fillTrafficPercentages(variants: ABTestVariant[]): ABTestConfig[] { } export function setupAB(config: SetupABConfig): ABTestSetupResult { - const { variants, storageKey = DEFAULT_STORAGE_KEY, controlId = "test", treatmentId = "production", sdk, pbjs } = config; + const { + variants, + storageKey = DEFAULT_STORAGE_KEY, + controlId = "test", + treatmentId = "production", + sdk, + pbjs, + } = config; // Process the provided variant config so that every variant has an explicit traffic percentage. // Variants without one share the remaining percentage equally.