import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest'; import { transformSync } from '@babel/core'; // eslint-disable-next-line @typescript-eslint/no-var-requires const kbachBabelPlugin = require('./index.js'); // No kbach.config.js exists at this test's cwd, so the plugin falls back to // the default theme and warns once (cached after) — expected, not a signal // this test is checking. let warnSpy: any; beforeAll(() => { warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); }); afterAll(() => { warnSpy.mockRestore(); }); // Regression coverage for a fixed bug: when the FIRST of two class-bearing // attributes on one element (e.g. `kb="group" className="p-4 bg-white"`) // resolved to zero styles on its own (like `group`, a standalone marker with // no styles of its own), the merge-tracking map (state.kbachElementInfo) // never got populated for that element — it fell through an early `return` // before the `.set()` call. The second attribute then never saw `existing`, // transformed itself in isolation, and the first attribute was left // completely untouched in the output (never renamed/removed), producing a // stray `kb="group"` prop reaching the real component at runtime instead of // being folded into the merge like the plugin's own doc comment promises. function transform(code: string): string { const result = transformSync(code, { filename: '/project/src/App.tsx', babelrc: false, configFile: false, parserOpts: { plugins: ['jsx'] }, plugins: [kbachBabelPlugin], }); if (!result?.code) throw new Error('transform produced no output'); return result.code; } describe('babel-plugin-kbach — merging multiple class attributes on one element', () => { it('merges two attributes that both have styles (baseline, already worked)', () => { const out = transform(`;`); expect(out).not.toContain('kb='); expect(out).not.toContain('className='); expect(out.match(/__kbachClasses/g)?.length).toBe(1); expect(out.match(/__kbachStyles/g)?.length).toBe(1); expect(out).toContain('__kbachClasses="p-4 bg-white"'); }); it('merges when the FIRST attribute alone resolves to no styles (the fixed bug)', () => { const out = transform(`;`); // The first, styleless attribute must be folded away, not left dangling. expect(out).not.toContain('kb='); expect(out).not.toContain('className='); // Exactly one merged pair, carrying BOTH classes. expect(out.match(/__kbachClasses/g)?.length).toBe(1); expect(out.match(/__kbachStyles/g)?.length).toBe(1); expect(out).toContain('__kbachClasses="group p-4 bg-white"'); }); it('merges when the SECOND attribute alone resolves to no styles', () => { const out = transform(`;`); expect(out).not.toContain('kb='); expect(out).not.toContain('className='); expect(out.match(/__kbachClasses/g)?.length).toBe(1); expect(out.match(/__kbachStyles/g)?.length).toBe(1); expect(out).toContain('__kbachClasses="p-4 bg-white group"'); }); it('leaves both attributes untouched when NEITHER resolves to any styles', () => { const out = transform(`;`); expect(out).toContain('kb="group"'); expect(out).toContain('className="peer"'); expect(out).not.toContain('__kbachClasses'); expect(out).not.toContain('__kbachStyles'); }); it('a single class attribute with styles still transforms normally (no merge involved)', () => { const out = transform(`;`); expect(out).not.toContain('className='); expect(out).toContain('__kbachClasses="p-4"'); expect(out.match(/__kbachStyles/g)?.length).toBe(1); }); it('a single class attribute with no styles is left untouched', () => { const out = transform(`;`); expect(out).toContain('className="group"'); expect(out).not.toContain('__kbachClasses'); }); });