@beeport/widget 0.1.0 → 0.1.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/dist/index.cjs CHANGED
@@ -34,6 +34,7 @@ function init(config) {
34
34
  throw new Error("BeePort widget requires a browser environment");
35
35
  }
36
36
  if (window.__beeport_loaded) return;
37
+ if (document.querySelector(`script[${SCRIPT_ATTR}]`)) return;
37
38
  const script = document.createElement("script");
38
39
  script.src = CDN_URL;
39
40
  script.setAttribute(SCRIPT_ATTR, "true");
@@ -51,11 +52,9 @@ function init(config) {
51
52
  script.setAttribute("data-text", config.text);
52
53
  }
53
54
  document.body.appendChild(script);
54
- window.__beeport_loaded = true;
55
55
  }
56
56
  function destroy() {
57
57
  if (typeof window === "undefined") return;
58
- window.dispatchEvent(new CustomEvent("beeport:destroy"));
59
58
  document.body.querySelectorAll(`script[${SCRIPT_ATTR}]`).forEach((el) => el.remove());
60
59
  document.body.querySelectorAll("[data-beeport-trigger]").forEach((el) => el.remove());
61
60
  document.body.querySelectorAll("[data-beeport-card]").forEach((el) => el.remove());
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@ function init(config) {
6
6
  throw new Error("BeePort widget requires a browser environment");
7
7
  }
8
8
  if (window.__beeport_loaded) return;
9
+ if (document.querySelector(`script[${SCRIPT_ATTR}]`)) return;
9
10
  const script = document.createElement("script");
10
11
  script.src = CDN_URL;
11
12
  script.setAttribute(SCRIPT_ATTR, "true");
@@ -23,11 +24,9 @@ function init(config) {
23
24
  script.setAttribute("data-text", config.text);
24
25
  }
25
26
  document.body.appendChild(script);
26
- window.__beeport_loaded = true;
27
27
  }
28
28
  function destroy() {
29
29
  if (typeof window === "undefined") return;
30
- window.dispatchEvent(new CustomEvent("beeport:destroy"));
31
30
  document.body.querySelectorAll(`script[${SCRIPT_ATTR}]`).forEach((el) => el.remove());
32
31
  document.body.querySelectorAll("[data-beeport-trigger]").forEach((el) => el.remove());
33
32
  document.body.querySelectorAll("[data-beeport-card]").forEach((el) => el.remove());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@beeport/widget",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "BeePort feedback widget — npm package wrapper around CDN widget.js",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -13,19 +13,11 @@
13
13
  "require": "./dist/index.cjs"
14
14
  }
15
15
  },
16
- "files": [
17
- "dist",
18
- "README.md"
19
- ],
20
- "publishConfig": {
21
- "access": "public"
22
- },
23
16
  "sideEffects": false,
24
17
  "scripts": {
25
18
  "test": "vitest run",
26
19
  "test:watch": "vitest",
27
- "build": "tsup src/index.ts --format esm,cjs --dts --clean",
28
- "prepack": "npm run build"
20
+ "build": "tsup src/index.ts --format esm,cjs --dts --clean"
29
21
  },
30
22
  "devDependencies": {
31
23
  "@types/node": "^22.0.0",
@@ -39,5 +31,8 @@
39
31
  "feedback",
40
32
  "widget"
41
33
  ],
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
42
37
  "license": "UNLICENSED"
43
38
  }
package/src/index.ts ADDED
@@ -0,0 +1,161 @@
1
+ /**
2
+ * @beeport/widget
3
+ *
4
+ * npm package wrapper around the CDN-served BeePort widget.
5
+ *
6
+ * Usage:
7
+ * import { init } from '@beeport/widget';
8
+ * init({ project: 'prj_abc123' });
9
+ *
10
+ * SSR-safe: no window/document access at module import time.
11
+ * All browser APIs are only accessed inside function bodies.
12
+ */
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // Types
16
+ // ---------------------------------------------------------------------------
17
+
18
+ export interface BeePortConfig {
19
+ /** Project API key (required). */
20
+ project: string;
21
+ /** Colour theme forwarded to the widget iframe. */
22
+ theme?: "light" | "dark" | "auto";
23
+ /** Position of the floating button. */
24
+ position?: "bottom-right" | "bottom-left";
25
+ /** Custom accent colour (any CSS colour string). */
26
+ accent?: string;
27
+ /** Custom button label text. */
28
+ text?: string;
29
+ }
30
+
31
+ // ---------------------------------------------------------------------------
32
+ // Constants
33
+ // ---------------------------------------------------------------------------
34
+
35
+ const CDN_URL = "https://cdn.beeport.ai/widget.js";
36
+ const SCRIPT_ATTR = "data-beeport-script";
37
+
38
+ // ---------------------------------------------------------------------------
39
+ // Exported API
40
+ // ---------------------------------------------------------------------------
41
+
42
+ /**
43
+ * Load and initialise the BeePort widget by injecting the CDN script tag.
44
+ *
45
+ * Idempotent — calling init() multiple times is safe; the second call is a
46
+ * no-op if the widget is already loaded.
47
+ *
48
+ * @throws Error if called in a non-browser environment (SSR guard).
49
+ */
50
+ export function init(config: BeePortConfig): void {
51
+ if (typeof window === "undefined") {
52
+ throw new Error("BeePort widget requires a browser environment");
53
+ }
54
+
55
+ // Deduplication guard — check both the loaded flag (set by CDN script after
56
+ // init) and whether we've already injected the script tag (covers the window
57
+ // between injection and CDN script execution).
58
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
59
+ if ((window as any).__beeport_loaded) return;
60
+ if (document.querySelector(`script[${SCRIPT_ATTR}]`)) return;
61
+
62
+ const script = document.createElement("script");
63
+ script.src = CDN_URL;
64
+ script.setAttribute(SCRIPT_ATTR, "true");
65
+ script.setAttribute("data-project", config.project);
66
+
67
+ if (config.theme !== undefined) {
68
+ script.setAttribute("data-theme", config.theme);
69
+ }
70
+ if (config.position !== undefined) {
71
+ script.setAttribute("data-position", config.position);
72
+ }
73
+ if (config.accent !== undefined) {
74
+ script.setAttribute("data-accent", config.accent);
75
+ }
76
+ if (config.text !== undefined) {
77
+ script.setAttribute("data-text", config.text);
78
+ }
79
+
80
+ document.body.appendChild(script);
81
+ }
82
+
83
+ /**
84
+ * Remove all widget DOM elements and reset the loaded flag.
85
+ *
86
+ * After calling destroy(), init() can be called again to re-initialise.
87
+ * Safe to call before init() or in non-browser environments (no-op).
88
+ */
89
+ export function destroy(): void {
90
+ if (typeof window === "undefined") return;
91
+
92
+ // Remove the injected script tag
93
+ document.body
94
+ .querySelectorAll(`script[${SCRIPT_ATTR}]`)
95
+ .forEach((el) => el.remove());
96
+
97
+ // Remove floating trigger button
98
+ document.body
99
+ .querySelectorAll("[data-beeport-trigger]")
100
+ .forEach((el) => el.remove());
101
+
102
+ // Remove desktop card
103
+ document.body
104
+ .querySelectorAll("[data-beeport-card]")
105
+ .forEach((el) => el.remove());
106
+
107
+ // Remove mobile bottom sheet
108
+ document.body
109
+ .querySelectorAll("[data-beeport-sheet]")
110
+ .forEach((el) => el.remove());
111
+
112
+ // Reset deduplication flag so init() can run again
113
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
114
+ delete (window as any).__beeport_loaded;
115
+ }
116
+
117
+ /**
118
+ * Programmatically open the feedback widget.
119
+ *
120
+ * Dispatches a `beeport:open` custom event on window. The loaded widget
121
+ * listens for this event and opens the iframe panel.
122
+ *
123
+ * Safe to call in non-browser environments (no-op).
124
+ */
125
+ export function open(): void {
126
+ if (typeof window === "undefined") return;
127
+ window.dispatchEvent(new CustomEvent("beeport:open"));
128
+ }
129
+
130
+ /**
131
+ * Programmatically close the feedback widget.
132
+ *
133
+ * Dispatches a `beeport:close` custom event on window. The loaded widget
134
+ * listens for this event and closes the iframe panel.
135
+ *
136
+ * Safe to call in non-browser environments (no-op).
137
+ */
138
+ export function close(): void {
139
+ if (typeof window === "undefined") return;
140
+ window.dispatchEvent(new CustomEvent("beeport:close"));
141
+ }
142
+
143
+ // ---------------------------------------------------------------------------
144
+ // BeeportWidget namespace (default export)
145
+ //
146
+ // Provides BeeportWidget.init() usage pattern in addition to named exports.
147
+ // Both patterns use the same underlying functions — no duplication.
148
+ //
149
+ // ESM: import BeeportWidget from '@beeport/widget'
150
+ // CJS: const BeeportWidget = require('@beeport/widget').default
151
+ // ---------------------------------------------------------------------------
152
+
153
+ /**
154
+ * BeeportWidget namespace — use this for the BeeportWidget.init() call pattern.
155
+ *
156
+ * All methods are identical to the named exports and are tree-shakeable when
157
+ * destructured (e.g. `const { init } = BeeportWidget`).
158
+ */
159
+ const BeeportWidget = { init, destroy, open, close } as const;
160
+
161
+ export default BeeportWidget;
@@ -0,0 +1,388 @@
1
+ /**
2
+ * Tests for @beeport/widget npm package.
3
+ *
4
+ * Covers:
5
+ * - SSR safety: no window/document access at import time
6
+ * - init(): script tag injection, config attributes, deduplication guard
7
+ * - destroy(): removes script tag and widget elements
8
+ * - open() / close(): dispatches custom events
9
+ * - TypeScript types (compile-time, verified by import shape)
10
+ */
11
+
12
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
13
+ import BeeportWidget, { init, destroy, open, close, type BeePortConfig } from "../src/index.js";
14
+
15
+ // ---------------------------------------------------------------------------
16
+ // Setup / teardown
17
+ // ---------------------------------------------------------------------------
18
+
19
+ beforeEach(() => {
20
+ document.body.replaceChildren();
21
+ document.head.replaceChildren();
22
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
23
+ delete (window as any).__beeport_loaded;
24
+ });
25
+
26
+ afterEach(() => {
27
+ document.body.replaceChildren();
28
+ document.head.replaceChildren();
29
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
30
+ delete (window as any).__beeport_loaded;
31
+ });
32
+
33
+ // ---------------------------------------------------------------------------
34
+ // SSR safety
35
+ // ---------------------------------------------------------------------------
36
+
37
+ describe("SSR safety", () => {
38
+ it("module exports init, destroy, open, close functions", () => {
39
+ // If import itself accessed window/document it would throw in Node.
40
+ // Since we are running in jsdom this verifies the shape at minimum.
41
+ expect(typeof init).toBe("function");
42
+ expect(typeof destroy).toBe("function");
43
+ expect(typeof open).toBe("function");
44
+ expect(typeof close).toBe("function");
45
+ });
46
+
47
+ it("init() throws a descriptive error when window is undefined", () => {
48
+ // Simulate SSR by temporarily removing window
49
+ const original = global.window;
50
+ // @ts-expect-error — intentionally deleting window for SSR test
51
+ delete global.window;
52
+
53
+ expect(() => init({ project: "prj_abc" })).toThrow(
54
+ "BeePort widget requires a browser environment"
55
+ );
56
+
57
+ // Restore window
58
+ global.window = original;
59
+ });
60
+ });
61
+
62
+ // ---------------------------------------------------------------------------
63
+ // init()
64
+ // ---------------------------------------------------------------------------
65
+
66
+ describe("init()", () => {
67
+ it("injects a <script> tag into document.body", () => {
68
+ init({ project: "prj_abc" });
69
+ const script = document.body.querySelector<HTMLScriptElement>(
70
+ "script[data-beeport-script]"
71
+ );
72
+ expect(script).not.toBeNull();
73
+ });
74
+
75
+ it("script src points to the CDN widget URL", () => {
76
+ init({ project: "prj_abc" });
77
+ const script = document.body.querySelector<HTMLScriptElement>(
78
+ "script[data-beeport-script]"
79
+ );
80
+ expect(script?.src).toBe("https://cdn.beeport.ai/widget.js");
81
+ });
82
+
83
+ it("sets data-project attribute on the script tag", () => {
84
+ init({ project: "prj_test123" });
85
+ const script = document.body.querySelector<HTMLScriptElement>(
86
+ "script[data-beeport-script]"
87
+ );
88
+ expect(script?.getAttribute("data-project")).toBe("prj_test123");
89
+ });
90
+
91
+ it("sets data-theme attribute when provided", () => {
92
+ init({ project: "prj_abc", theme: "dark" });
93
+ const script = document.body.querySelector<HTMLScriptElement>(
94
+ "script[data-beeport-script]"
95
+ );
96
+ expect(script?.getAttribute("data-theme")).toBe("dark");
97
+ });
98
+
99
+ it("does not set data-theme when not provided", () => {
100
+ init({ project: "prj_abc" });
101
+ const script = document.body.querySelector<HTMLScriptElement>(
102
+ "script[data-beeport-script]"
103
+ );
104
+ expect(script?.hasAttribute("data-theme")).toBe(false);
105
+ });
106
+
107
+ it("sets data-position when provided", () => {
108
+ init({ project: "prj_abc", position: "bottom-left" });
109
+ const script = document.body.querySelector<HTMLScriptElement>(
110
+ "script[data-beeport-script]"
111
+ );
112
+ expect(script?.getAttribute("data-position")).toBe("bottom-left");
113
+ });
114
+
115
+ it("does not set data-position when not provided", () => {
116
+ init({ project: "prj_abc" });
117
+ const script = document.body.querySelector<HTMLScriptElement>(
118
+ "script[data-beeport-script]"
119
+ );
120
+ expect(script?.hasAttribute("data-position")).toBe(false);
121
+ });
122
+
123
+ it("sets data-accent when provided", () => {
124
+ init({ project: "prj_abc", accent: "#ff0000" });
125
+ const script = document.body.querySelector<HTMLScriptElement>(
126
+ "script[data-beeport-script]"
127
+ );
128
+ expect(script?.getAttribute("data-accent")).toBe("#ff0000");
129
+ });
130
+
131
+ it("does not set data-accent when not provided", () => {
132
+ init({ project: "prj_abc" });
133
+ const script = document.body.querySelector<HTMLScriptElement>(
134
+ "script[data-beeport-script]"
135
+ );
136
+ expect(script?.hasAttribute("data-accent")).toBe(false);
137
+ });
138
+
139
+ it("sets data-text when provided", () => {
140
+ init({ project: "prj_abc", text: "Feedback" });
141
+ const script = document.body.querySelector<HTMLScriptElement>(
142
+ "script[data-beeport-script]"
143
+ );
144
+ expect(script?.getAttribute("data-text")).toBe("Feedback");
145
+ });
146
+
147
+ it("does not set data-text when not provided", () => {
148
+ init({ project: "prj_abc" });
149
+ const script = document.body.querySelector<HTMLScriptElement>(
150
+ "script[data-beeport-script]"
151
+ );
152
+ expect(script?.hasAttribute("data-text")).toBe(false);
153
+ });
154
+
155
+ it("does not set window.__beeport_loaded (CDN script sets it after actual init)", () => {
156
+ init({ project: "prj_abc" });
157
+ // The NPM wrapper no longer sets __beeport_loaded — the CDN widget.js
158
+ // sets it after creating the button. This avoids a race condition where
159
+ // the flag blocks the CDN script's autoInit.
160
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
161
+ expect((window as any).__beeport_loaded).toBeUndefined();
162
+ });
163
+
164
+ it("does not inject a second script if already loaded", () => {
165
+ init({ project: "prj_abc" });
166
+ init({ project: "prj_abc" });
167
+ const scripts = document.body.querySelectorAll("script[data-beeport-script]");
168
+ expect(scripts).toHaveLength(1);
169
+ });
170
+
171
+ it("returns early if __beeport_loaded is pre-set", () => {
172
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
173
+ (window as any).__beeport_loaded = true;
174
+ init({ project: "prj_abc" });
175
+ const scripts = document.body.querySelectorAll("script[data-beeport-script]");
176
+ expect(scripts).toHaveLength(0);
177
+ });
178
+
179
+ it("accepts all optional config fields without throwing", () => {
180
+ const config: BeePortConfig = {
181
+ project: "prj_abc",
182
+ theme: "auto",
183
+ position: "bottom-right",
184
+ accent: "#333333",
185
+ text: "Send feedback",
186
+ };
187
+ expect(() => init(config)).not.toThrow();
188
+ });
189
+ });
190
+
191
+ // ---------------------------------------------------------------------------
192
+ // destroy()
193
+ // ---------------------------------------------------------------------------
194
+
195
+ describe("destroy()", () => {
196
+ it("removes the injected script tag", () => {
197
+ init({ project: "prj_abc" });
198
+ destroy();
199
+ const script = document.body.querySelector("script[data-beeport-script]");
200
+ expect(script).toBeNull();
201
+ });
202
+
203
+ it("resets the __beeport_loaded flag", () => {
204
+ init({ project: "prj_abc" });
205
+ destroy();
206
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
207
+ expect((window as any).__beeport_loaded).toBeUndefined();
208
+ });
209
+
210
+ it("removes the floating button element if present", () => {
211
+ init({ project: "prj_abc" });
212
+ // Simulate widget creating a button
213
+ const btn = document.createElement("button");
214
+ btn.setAttribute("data-beeport-trigger", "true");
215
+ document.body.appendChild(btn);
216
+ destroy();
217
+ const remaining = document.body.querySelector("[data-beeport-trigger]");
218
+ expect(remaining).toBeNull();
219
+ });
220
+
221
+ it("removes any iframe card if open", () => {
222
+ init({ project: "prj_abc" });
223
+ // Simulate an open card
224
+ const card = document.createElement("div");
225
+ card.setAttribute("data-beeport-card", "true");
226
+ document.body.appendChild(card);
227
+ destroy();
228
+ const remaining = document.body.querySelector("[data-beeport-card]");
229
+ expect(remaining).toBeNull();
230
+ });
231
+
232
+ it("removes any mobile sheet if open", () => {
233
+ init({ project: "prj_abc" });
234
+ const sheet = document.createElement("div");
235
+ sheet.setAttribute("data-beeport-sheet", "true");
236
+ document.body.appendChild(sheet);
237
+ destroy();
238
+ const remaining = document.body.querySelector("[data-beeport-sheet]");
239
+ expect(remaining).toBeNull();
240
+ });
241
+
242
+ it("does not throw when called before init", () => {
243
+ expect(() => destroy()).not.toThrow();
244
+ });
245
+
246
+ it("does not throw when window is undefined", () => {
247
+ const original = global.window;
248
+ // @ts-expect-error — intentionally removing window for SSR test
249
+ delete global.window;
250
+ expect(() => destroy()).not.toThrow();
251
+ global.window = original;
252
+ });
253
+
254
+ it("allows re-initialisation after destroy", () => {
255
+ init({ project: "prj_abc" });
256
+ destroy();
257
+ init({ project: "prj_abc" });
258
+ const script = document.body.querySelector("script[data-beeport-script]");
259
+ expect(script).not.toBeNull();
260
+ });
261
+ });
262
+
263
+ // ---------------------------------------------------------------------------
264
+ // open()
265
+ // ---------------------------------------------------------------------------
266
+
267
+ describe("open()", () => {
268
+ it("dispatches a beeport:open custom event on window", () => {
269
+ const events: Event[] = [];
270
+ window.addEventListener("beeport:open", (e) => events.push(e));
271
+
272
+ open();
273
+
274
+ expect(events).toHaveLength(1);
275
+ window.removeEventListener("beeport:open", (e) => events.push(e));
276
+ });
277
+
278
+ it("does not throw when window is undefined", () => {
279
+ const original = global.window;
280
+ // @ts-expect-error — intentionally removing window
281
+ delete global.window;
282
+ expect(() => open()).not.toThrow();
283
+ global.window = original;
284
+ });
285
+ });
286
+
287
+ // ---------------------------------------------------------------------------
288
+ // close()
289
+ // ---------------------------------------------------------------------------
290
+
291
+ describe("close()", () => {
292
+ it("dispatches a beeport:close custom event on window", () => {
293
+ const events: Event[] = [];
294
+ window.addEventListener("beeport:close", (e) => events.push(e));
295
+
296
+ close();
297
+
298
+ expect(events).toHaveLength(1);
299
+ window.removeEventListener("beeport:close", (e) => events.push(e));
300
+ });
301
+
302
+ it("does not throw when window is undefined", () => {
303
+ const original = global.window;
304
+ // @ts-expect-error — intentionally removing window
305
+ delete global.window;
306
+ expect(() => close()).not.toThrow();
307
+ global.window = original;
308
+ });
309
+ });
310
+
311
+ // ---------------------------------------------------------------------------
312
+ // BeeportWidget namespace export (default export)
313
+ // ---------------------------------------------------------------------------
314
+
315
+ describe("BeeportWidget namespace (default export)", () => {
316
+ it("exports a BeeportWidget object with init method", () => {
317
+ expect(typeof BeeportWidget).toBe("object");
318
+ expect(typeof BeeportWidget.init).toBe("function");
319
+ });
320
+
321
+ it("BeeportWidget.init() injects a <script> tag into document.body", () => {
322
+ BeeportWidget.init({ project: "prj_ns_test" });
323
+ const script = document.body.querySelector<HTMLScriptElement>(
324
+ "script[data-beeport-script]"
325
+ );
326
+ expect(script).not.toBeNull();
327
+ expect(script?.getAttribute("data-project")).toBe("prj_ns_test");
328
+ });
329
+
330
+ it("BeeportWidget.destroy() removes widget elements", () => {
331
+ BeeportWidget.init({ project: "prj_ns_test" });
332
+ BeeportWidget.destroy();
333
+ const script = document.body.querySelector("script[data-beeport-script]");
334
+ expect(script).toBeNull();
335
+ });
336
+
337
+ it("BeeportWidget.open() dispatches beeport:open event", () => {
338
+ const events: Event[] = [];
339
+ window.addEventListener("beeport:open", (e) => events.push(e));
340
+ BeeportWidget.open();
341
+ expect(events).toHaveLength(1);
342
+ window.removeEventListener("beeport:open", (e) => events.push(e));
343
+ });
344
+
345
+ it("BeeportWidget.close() dispatches beeport:close event", () => {
346
+ const events: Event[] = [];
347
+ window.addEventListener("beeport:close", (e) => events.push(e));
348
+ BeeportWidget.close();
349
+ expect(events).toHaveLength(1);
350
+ window.removeEventListener("beeport:close", (e) => events.push(e));
351
+ });
352
+
353
+ it("BeeportWidget.init() is identical to named init()", () => {
354
+ // Both should be the same function reference
355
+ expect(BeeportWidget.init).toBe(init);
356
+ expect(BeeportWidget.destroy).toBe(destroy);
357
+ expect(BeeportWidget.open).toBe(open);
358
+ expect(BeeportWidget.close).toBe(close);
359
+ });
360
+ });
361
+
362
+ // ---------------------------------------------------------------------------
363
+ // TypeScript type validation (compile-time only — these just need to compile)
364
+ // ---------------------------------------------------------------------------
365
+
366
+ describe("TypeScript types", () => {
367
+ it("BeePortConfig requires project field", () => {
368
+ // This is a type-level test — just verifying config shape at runtime
369
+ const config: BeePortConfig = { project: "prj_required" };
370
+ expect(config.project).toBe("prj_required");
371
+ });
372
+
373
+ it("BeePortConfig theme accepts light | dark | auto", () => {
374
+ const light: BeePortConfig = { project: "p", theme: "light" };
375
+ const dark: BeePortConfig = { project: "p", theme: "dark" };
376
+ const auto: BeePortConfig = { project: "p", theme: "auto" };
377
+ expect(light.theme).toBe("light");
378
+ expect(dark.theme).toBe("dark");
379
+ expect(auto.theme).toBe("auto");
380
+ });
381
+
382
+ it("BeePortConfig position accepts bottom-right | bottom-left", () => {
383
+ const right: BeePortConfig = { project: "p", position: "bottom-right" };
384
+ const left: BeePortConfig = { project: "p", position: "bottom-left" };
385
+ expect(right.position).toBe("bottom-right");
386
+ expect(left.position).toBe("bottom-left");
387
+ });
388
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
+ "strict": true,
8
+ "noUncheckedIndexedAccess": true,
9
+ "noImplicitOverride": true,
10
+ "exactOptionalPropertyTypes": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "skipLibCheck": true,
13
+ "outDir": "dist",
14
+ "rootDir": ".",
15
+ "declaration": true,
16
+ "declarationDir": "dist",
17
+ "types": ["node"]
18
+ },
19
+ "include": ["src/**/*", "test/**/*"],
20
+ "exclude": ["node_modules", "dist"]
21
+ }
@@ -0,0 +1,13 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ environment: "jsdom",
6
+ globals: false,
7
+ include: ["test/**/*.test.ts"],
8
+ coverage: {
9
+ provider: "v8",
10
+ include: ["src/**/*.ts"],
11
+ },
12
+ },
13
+ });