feat(config): parse data-shadow attribute on script tag
Adds the boolean field consumed by the upcoming Shadow DOM mount path. Strict equality with the string "true" — anything else (absent, "false", "1", "yes", empty) yields false, so accidental opt-in is impossible. Tests cover the full parseConfig surface (required fields, server-url derivation, position default) plus the new shadow attribute parsing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ca084735ac
commit
8d0696584c
@ -3,6 +3,7 @@ export type WidgetConfig = {
|
|||||||
apiKey: string;
|
apiKey: string;
|
||||||
serverUrl: string;
|
serverUrl: string;
|
||||||
position?: 'bottom-right' | 'bottom-left';
|
position?: 'bottom-right' | 'bottom-left';
|
||||||
|
shadow: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function parseConfig(): WidgetConfig | null {
|
export function parseConfig(): WidgetConfig | null {
|
||||||
@ -22,10 +23,11 @@ export function parseConfig(): WidgetConfig | null {
|
|||||||
| 'bottom-right'
|
| 'bottom-right'
|
||||||
| 'bottom-left'
|
| 'bottom-left'
|
||||||
| null) ?? 'bottom-right';
|
| null) ?? 'bottom-right';
|
||||||
|
const shadow = script.getAttribute('data-shadow') === 'true';
|
||||||
|
|
||||||
if (!botId || !apiKey) return null;
|
if (!botId || !apiKey) return null;
|
||||||
|
|
||||||
return { botId, apiKey, serverUrl, position };
|
return { botId, apiKey, serverUrl, position, shadow };
|
||||||
}
|
}
|
||||||
|
|
||||||
function deriveServerFromSrc(src: string): string {
|
function deriveServerFromSrc(src: string): string {
|
||||||
|
|||||||
118
tests/unit/config.test.ts
Normal file
118
tests/unit/config.test.ts
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import { describe, it, expect, beforeEach } from 'vitest';
|
||||||
|
import { parseConfig } from '@/config';
|
||||||
|
|
||||||
|
function createScript(
|
||||||
|
attrs: Record<string, string>,
|
||||||
|
src = 'https://cdn.example.com/messenzy-widget.iife.js',
|
||||||
|
): HTMLScriptElement {
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.src = src;
|
||||||
|
for (const [key, value] of Object.entries(attrs)) {
|
||||||
|
script.setAttribute(key, value);
|
||||||
|
}
|
||||||
|
document.head.appendChild(script);
|
||||||
|
return script;
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
document.head.innerHTML = '';
|
||||||
|
document.body.innerHTML = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('parseConfig', () => {
|
||||||
|
it('returns null when no script with data-bot-id is present', () => {
|
||||||
|
expect(parseConfig()).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns null when data-api-key is missing', () => {
|
||||||
|
createScript({ 'data-bot-id': 'b1' });
|
||||||
|
expect(parseConfig()).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns null when data-bot-id is missing', () => {
|
||||||
|
createScript({ 'data-api-key': 'k1' });
|
||||||
|
expect(parseConfig()).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parses required fields', () => {
|
||||||
|
createScript({ 'data-bot-id': 'b1', 'data-api-key': 'k1' });
|
||||||
|
const config = parseConfig();
|
||||||
|
expect(config).not.toBeNull();
|
||||||
|
expect(config?.botId).toBe('b1');
|
||||||
|
expect(config?.apiKey).toBe('k1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('derives serverUrl from script src when data-server-url absent', () => {
|
||||||
|
createScript(
|
||||||
|
{ 'data-bot-id': 'b1', 'data-api-key': 'k1' },
|
||||||
|
'https://widgets.example.com/path/messenzy.iife.js',
|
||||||
|
);
|
||||||
|
expect(parseConfig()?.serverUrl).toBe('https://widgets.example.com');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('respects explicit data-server-url override', () => {
|
||||||
|
createScript({
|
||||||
|
'data-bot-id': 'b1',
|
||||||
|
'data-api-key': 'k1',
|
||||||
|
'data-server-url': 'https://api.foo.test',
|
||||||
|
});
|
||||||
|
expect(parseConfig()?.serverUrl).toBe('https://api.foo.test');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('defaults position to bottom-right', () => {
|
||||||
|
createScript({ 'data-bot-id': 'b1', 'data-api-key': 'k1' });
|
||||||
|
expect(parseConfig()?.position).toBe('bottom-right');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parses position bottom-left', () => {
|
||||||
|
createScript({
|
||||||
|
'data-bot-id': 'b1',
|
||||||
|
'data-api-key': 'k1',
|
||||||
|
'data-position': 'bottom-left',
|
||||||
|
});
|
||||||
|
expect(parseConfig()?.position).toBe('bottom-left');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('shadow attribute (PR B opt-in)', () => {
|
||||||
|
it('shadow=false when data-shadow absent', () => {
|
||||||
|
createScript({ 'data-bot-id': 'b1', 'data-api-key': 'k1' });
|
||||||
|
expect(parseConfig()?.shadow).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shadow=true when data-shadow="true"', () => {
|
||||||
|
createScript({
|
||||||
|
'data-bot-id': 'b1',
|
||||||
|
'data-api-key': 'k1',
|
||||||
|
'data-shadow': 'true',
|
||||||
|
});
|
||||||
|
expect(parseConfig()?.shadow).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shadow=false when data-shadow="false"', () => {
|
||||||
|
createScript({
|
||||||
|
'data-bot-id': 'b1',
|
||||||
|
'data-api-key': 'k1',
|
||||||
|
'data-shadow': 'false',
|
||||||
|
});
|
||||||
|
expect(parseConfig()?.shadow).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shadow=false when data-shadow="" (empty)', () => {
|
||||||
|
createScript({
|
||||||
|
'data-bot-id': 'b1',
|
||||||
|
'data-api-key': 'k1',
|
||||||
|
'data-shadow': '',
|
||||||
|
});
|
||||||
|
expect(parseConfig()?.shadow).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shadow=false for unrecognized values (e.g. "1", "yes")', () => {
|
||||||
|
createScript({
|
||||||
|
'data-bot-id': 'b1',
|
||||||
|
'data-api-key': 'k1',
|
||||||
|
'data-shadow': '1',
|
||||||
|
});
|
||||||
|
expect(parseConfig()?.shadow).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user