{"version":3,"sources":["../packages/domain/testing/src/index.ts"],"sourcesContent":["/**\n * tailwind-styled-v4 — Testing Utilities\n *\n * Integrasi dengan Jest / Vitest untuk test komponen tw().\n *\n * @example\n * // vitest.config.ts\n * import { tailwindStyledSetup } from '@tailwind-styled/testing'\n * export default defineConfig({ test: { setupFiles: ['@tailwind-styled/testing/setup'] } })\n *\n * // Button.test.ts\n * import { render } from '@testing-library/react'\n * import { expectClasses, getVariantClass } from '@tailwind-styled/testing'\n *\n * test('Button renders primary variant', () => {\n *   const { container } = render(<Button intent=\"primary\" />)\n *   expectClasses(container.firstChild, ['bg-blue-500', 'text-white'])\n * })\n */\n\n// ─── Jest/Vitest custom matchers ─────────────────────────────────────────────\n\n/**\n * Custom matcher: toHaveClass\n * Dipakai langsung atau via expect.extend(tailwindMatchers)\n *\n * @example\n * expect(element).toHaveClass('bg-blue-500')\n */\nexport function toHaveClass(className: string) {\n  return (value: { classList?: { contains: (name: string) => boolean } }) => {\n    const pass = Boolean(value?.classList?.contains(className))\n    return {\n      pass,\n      message: () => `expected element ${pass ? \"not \" : \"\"}to contain class '${className}'`,\n    }\n  }\n}\n\n/**\n * Custom matcher: toHaveClasses\n * Cek beberapa class sekaligus\n *\n * @example\n * expect(element).toHaveClasses(['px-4', 'py-2', 'rounded'])\n */\nexport function toHaveClasses(classNames: string[]) {\n  return (value: Element | null | undefined) => {\n    if (!value) return { pass: false, message: () => \"element is null or undefined\" }\n    const missing = classNames.filter((c) => !value.classList.contains(c))\n    const pass = missing.length === 0\n    return {\n      pass,\n      message: () =>\n        pass\n          ? `expected element not to have all classes: ${classNames.join(\", \")}`\n          : `expected element to have classes: ${missing.join(\", \")}`,\n    }\n  }\n}\n\n/**\n * Custom matcher: toNotHaveClass\n *\n * @example\n * expect(element).toNotHaveClass('hidden')\n */\nexport function toNotHaveClass(className: string) {\n  return (value: Element | null | undefined) => {\n    if (!value) return { pass: false, message: () => \"element is null or undefined\" }\n    const pass = !value.classList.contains(className)\n    return {\n      pass,\n      message: () => `expected element ${pass ? \"\" : \"not \"}to have class '${className}'`,\n    }\n  }\n}\n\n/** Semua matchers — pakai dengan expect.extend(tailwindMatchers) */\nexport const tailwindMatchers = { toHaveClass, toHaveClasses, toNotHaveClass }\n\n// ─── Helper utilities ────────────────────────────────────────────────────────\n\n/**\n * Assert element punya semua class yang diharapkan.\n * Lebih ergonomis dari `expect(el).toHaveClasses([...])` untuk banyak class.\n *\n * @example\n * const { container } = render(<Button intent=\"primary\" size=\"lg\" />)\n * expectClasses(container.firstChild, ['bg-blue-500', 'text-white', 'h-12'])\n */\nexport function expectClasses(element: Element | null | undefined, classes: string[]): void {\n  if (!element) throw new Error(\"expectClasses: element is null or undefined\")\n  const missing = classes.filter((c) => !element.classList.contains(c))\n  if (missing.length > 0) {\n    throw new Error(\n      `Expected element to have classes: ${missing.join(\", \")}\\n` +\n        `  Actual classes: ${element.className}`\n    )\n  }\n}\n\n/**\n * Assert element tidak punya class tertentu.\n *\n * @example\n * expectNoClasses(container.firstChild, ['opacity-50', 'cursor-not-allowed'])\n */\nexport function expectNoClasses(element: Element | null | undefined, classes: string[]): void {\n  if (!element) throw new Error(\"expectNoClasses: element is null or undefined\")\n  const found = classes.filter((c) => element.classList.contains(c))\n  if (found.length > 0) {\n    throw new Error(\n      `Expected element NOT to have classes: ${found.join(\", \")}\\n` +\n        `  Actual classes: ${element.className}`\n    )\n  }\n}\n\n/**\n * Extract class list dari element sebagai sorted array.\n * Berguna untuk snapshot testing.\n *\n * @example\n * expect(getClassList(element)).toMatchSnapshot()\n */\nexport function getClassList(element: Element | null | undefined): string[] {\n  if (!element) return []\n  // classList mungkin tidak iterable di test environment (mock object)\n  // Fallback ke parsing className string jika Array.from gagal atau hasilkan []\n  try {\n    const list = Array.from(element.classList)\n    if (list.length > 0) return list.sort()\n  } catch {}\n  // Fallback: parse dari className string\n  if (element.className && typeof element.className === \"string\") {\n    return element.className.trim().split(/\\s+/).filter(Boolean).sort()\n  }\n  return []\n}\n\n// ─── Variant snapshot helpers ────────────────────────────────────────────────\n\n/**\n * Buat snapshot dari semua kombinasi variant.\n * Berguna untuk regression testing pada styled components.\n *\n * @example\n * const buttonVariants = snapshotVariants(\n *   (props) => {\n *     const { container } = render(<Button {...props} />)\n *     return container.firstChild?.className ?? ''\n *   },\n *   [\n *     { intent: 'primary', size: 'sm' },\n *     { intent: 'primary', size: 'lg' },\n *     { intent: 'danger',  size: 'sm' },\n *   ]\n * )\n * expect(buttonVariants).toMatchSnapshot()\n */\nexport function snapshotVariants<T>(render: (variant: T) => string, variants: T[]) {\n  return variants.map((variant) => ({\n    variant,\n    output: render(variant),\n  }))\n}\n\n/**\n * Generate semua kombinasi dari variant matrix.\n *\n * @example\n * const combinations = expandVariantMatrix({\n *   intent: ['primary', 'danger'],\n *   size: ['sm', 'md', 'lg'],\n *   disabled: [true, false],\n * })\n * // → 2 × 3 × 2 = 12 kombinasi\n */\nexport function expandVariantMatrix(\n  matrix: Record<string, Array<string | number | boolean>>\n): Array<Record<string, string | number | boolean>> {\n  const keys = Object.keys(matrix)\n  if (keys.length === 0) return [{}]\n\n  const result: Array<Record<string, string | number | boolean>> = []\n\n  function walk(index: number, current: Record<string, string | number | boolean>) {\n    if (index >= keys.length) {\n      result.push({ ...current })\n      return\n    }\n    const key = keys[index]!\n    for (const value of matrix[key] ?? []) {\n      current[key] = value\n      walk(index + 1, current)\n    }\n  }\n\n  walk(0, {})\n  return result\n}\n\n/**\n * Test semua kombinasi variant sekaligus — no missing coverage.\n *\n * @example\n * testAllVariants(\n *   Button,\n *   { intent: ['primary','danger'], size: ['sm','lg'] },\n *   (el, variant) => {\n *     expect(el).not.toBeNull()\n *     if (variant.intent === 'primary') expectClasses(el, ['bg-blue-500'])\n *   }\n * )\n */\nexport function testAllVariants(\n  matrix: Record<string, Array<string | number | boolean>>,\n  testFn: (variant: Record<string, string | number | boolean>) => void\n): void {\n  const combinations = expandVariantMatrix(matrix)\n  for (const variant of combinations) {\n    testFn(variant)\n  }\n}\n\n// ─── CSS-in-JS output assertions ─────────────────────────────────────────────\n\n/**\n * Bandingkan dua class string secara semantik (urutan tidak penting).\n *\n * @example\n * expectClassesEqual('px-4 py-2 bg-blue-500', 'bg-blue-500 px-4 py-2') // pass\n */\nexport function expectClassesEqual(actual: string, expected: string): void {\n  const actualSet = new Set(actual.trim().split(/\\s+/).filter(Boolean))\n  const expectedSet = new Set(expected.trim().split(/\\s+/).filter(Boolean))\n\n  const missing = [...expectedSet].filter((c) => !actualSet.has(c))\n  const extra = [...actualSet].filter((c) => !expectedSet.has(c))\n\n  if (missing.length > 0 || extra.length > 0) {\n    const parts: string[] = []\n    if (missing.length > 0) parts.push(`Missing: ${missing.join(\", \")}`)\n    if (extra.length > 0) parts.push(`Extra:   ${extra.join(\", \")}`)\n    throw new Error(`Class mismatch:\\n  ${parts.join(\"\\n  \")}`)\n  }\n}\n\n// ─── Engine metrics matchers ──────────────────────────────────────────────────\n\nexport interface EngineMetricsSnapshot {\n  totalFiles?: number\n  uniqueClasses?: number\n  buildTimeMs?: number\n  cssBytes?: number\n  cacheHits?: number\n  cacheMisses?: number\n  incrementalRuns?: number\n  fullRescans?: number\n}\n\n/**\n * Assert engine metrics snapshot meets minimum thresholds.\n *\n * @example\n * const result = await engine.build()\n * expectEngineMetrics(metrics.snapshot(), {\n *   totalFiles: 10,\n *   buildTimeMs: 5000, // max\n * })\n */\nexport function expectEngineMetrics(\n  metrics: EngineMetricsSnapshot,\n  expectations: {\n    minFiles?: number\n    maxBuildTimeMs?: number\n    minUniqueClasses?: number\n    cacheHitRateMin?: number // 0–1\n  }\n): void {\n  if (expectations.minFiles !== undefined) {\n    const actual = metrics.totalFiles ?? 0\n    if (actual < expectations.minFiles) {\n      throw new Error(\n        `Engine metrics: expected at least ${expectations.minFiles} files, got ${actual}`\n      )\n    }\n  }\n\n  if (expectations.maxBuildTimeMs !== undefined) {\n    const actual = metrics.buildTimeMs ?? 0\n    if (actual > expectations.maxBuildTimeMs) {\n      throw new Error(\n        `Engine metrics: build took ${actual}ms, expected ≤ ${expectations.maxBuildTimeMs}ms`\n      )\n    }\n  }\n\n  if (expectations.minUniqueClasses !== undefined) {\n    const actual = metrics.uniqueClasses ?? 0\n    if (actual < expectations.minUniqueClasses) {\n      throw new Error(\n        `Engine metrics: expected at least ${expectations.minUniqueClasses} unique classes, got ${actual}`\n      )\n    }\n  }\n\n  if (expectations.cacheHitRateMin !== undefined) {\n    const hits = metrics.cacheHits ?? 0\n    const total = hits + (metrics.cacheMisses ?? 0)\n    const rate = total > 0 ? hits / total : 0\n    if (rate < expectations.cacheHitRateMin) {\n      throw new Error(\n        `Engine metrics: cache hit rate ${(rate * 100).toFixed(1)}%, expected ≥ ${(expectations.cacheHitRateMin * 100).toFixed(1)}%`\n      )\n    }\n  }\n}\n\n/**\n * Custom Jest/Vitest matcher: toHaveEngineMetrics\n *\n * @example\n * expect(metrics).toHaveEngineMetrics({ minFiles: 1 })\n */\nexport function toHaveEngineMetrics(expectations: Parameters<typeof expectEngineMetrics>[1]) {\n  return (metrics: EngineMetricsSnapshot) => {\n    try {\n      expectEngineMetrics(metrics, expectations)\n      return { pass: true, message: () => \"engine metrics matched expectations\" }\n    } catch (e: unknown) {\n      const msg = e instanceof Error ? e.message : String(e)\n      return { pass: false, message: () => msg }\n    }\n  }\n}\n\n/** All matchers including engine metrics */\nexport const tailwindMatchersWithMetrics = {\n  toHaveClass,\n  toHaveClasses,\n  toNotHaveClass,\n  toHaveEngineMetrics,\n}"],"mappings":";AA6BO,SAAS,YAAY,WAAmB;AAC7C,SAAO,CAAC,UAAmE;AACzE,UAAM,OAAO,QAAQ,OAAO,WAAW,SAAS,SAAS,CAAC;AAC1D,WAAO;AAAA,MACL;AAAA,MACA,SAAS,MAAM,oBAAoB,OAAO,SAAS,EAAE,qBAAqB,SAAS;AAAA,IACrF;AAAA,EACF;AACF;AASO,SAAS,cAAc,YAAsB;AAClD,SAAO,CAAC,UAAsC;AAC5C,QAAI,CAAC,MAAO,QAAO,EAAE,MAAM,OAAO,SAAS,MAAM,+BAA+B;AAChF,UAAM,UAAU,WAAW,OAAO,CAAC,MAAM,CAAC,MAAM,UAAU,SAAS,CAAC,CAAC;AACrE,UAAM,OAAO,QAAQ,WAAW;AAChC,WAAO;AAAA,MACL;AAAA,MACA,SAAS,MACP,OACI,6CAA6C,WAAW,KAAK,IAAI,CAAC,KAClE,qCAAqC,QAAQ,KAAK,IAAI,CAAC;AAAA,IAC/D;AAAA,EACF;AACF;AAQO,SAAS,eAAe,WAAmB;AAChD,SAAO,CAAC,UAAsC;AAC5C,QAAI,CAAC,MAAO,QAAO,EAAE,MAAM,OAAO,SAAS,MAAM,+BAA+B;AAChF,UAAM,OAAO,CAAC,MAAM,UAAU,SAAS,SAAS;AAChD,WAAO;AAAA,MACL;AAAA,MACA,SAAS,MAAM,oBAAoB,OAAO,KAAK,MAAM,kBAAkB,SAAS;AAAA,IAClF;AAAA,EACF;AACF;AAGO,IAAM,mBAAmB,EAAE,aAAa,eAAe,eAAe;AAYtE,SAAS,cAAc,SAAqC,SAAyB;AAC1F,MAAI,CAAC,QAAS,OAAM,IAAI,MAAM,6CAA6C;AAC3E,QAAM,UAAU,QAAQ,OAAO,CAAC,MAAM,CAAC,QAAQ,UAAU,SAAS,CAAC,CAAC;AACpE,MAAI,QAAQ,SAAS,GAAG;AACtB,UAAM,IAAI;AAAA,MACR,qCAAqC,QAAQ,KAAK,IAAI,CAAC;AAAA,oBAChC,QAAQ,SAAS;AAAA,IAC1C;AAAA,EACF;AACF;AAQO,SAAS,gBAAgB,SAAqC,SAAyB;AAC5F,MAAI,CAAC,QAAS,OAAM,IAAI,MAAM,+CAA+C;AAC7E,QAAM,QAAQ,QAAQ,OAAO,CAAC,MAAM,QAAQ,UAAU,SAAS,CAAC,CAAC;AACjE,MAAI,MAAM,SAAS,GAAG;AACpB,UAAM,IAAI;AAAA,MACR,yCAAyC,MAAM,KAAK,IAAI,CAAC;AAAA,oBAClC,QAAQ,SAAS;AAAA,IAC1C;AAAA,EACF;AACF;AASO,SAAS,aAAa,SAA+C;AAC1E,MAAI,CAAC,QAAS,QAAO,CAAC;AAGtB,MAAI;AACF,UAAM,OAAO,MAAM,KAAK,QAAQ,SAAS;AACzC,QAAI,KAAK,SAAS,EAAG,QAAO,KAAK,KAAK;AAAA,EACxC,QAAQ;AAAA,EAAC;AAET,MAAI,QAAQ,aAAa,OAAO,QAAQ,cAAc,UAAU;AAC9D,WAAO,QAAQ,UAAU,KAAK,EAAE,MAAM,KAAK,EAAE,OAAO,OAAO,EAAE,KAAK;AAAA,EACpE;AACA,SAAO,CAAC;AACV;AAsBO,SAAS,iBAAoB,QAAgC,UAAe;AACjF,SAAO,SAAS,IAAI,CAAC,aAAa;AAAA,IAChC;AAAA,IACA,QAAQ,OAAO,OAAO;AAAA,EACxB,EAAE;AACJ;AAaO,SAAS,oBACd,QACkD;AAClD,QAAM,OAAO,OAAO,KAAK,MAAM;AAC/B,MAAI,KAAK,WAAW,EAAG,QAAO,CAAC,CAAC,CAAC;AAEjC,QAAM,SAA2D,CAAC;AAElE,WAAS,KAAK,OAAe,SAAoD;AAC/E,QAAI,SAAS,KAAK,QAAQ;AACxB,aAAO,KAAK,EAAE,GAAG,QAAQ,CAAC;AAC1B;AAAA,IACF;AACA,UAAM,MAAM,KAAK,KAAK;AACtB,eAAW,SAAS,OAAO,GAAG,KAAK,CAAC,GAAG;AACrC,cAAQ,GAAG,IAAI;AACf,WAAK,QAAQ,GAAG,OAAO;AAAA,IACzB;AAAA,EACF;AAEA,OAAK,GAAG,CAAC,CAAC;AACV,SAAO;AACT;AAeO,SAAS,gBACd,QACA,QACM;AACN,QAAM,eAAe,oBAAoB,MAAM;AAC/C,aAAW,WAAW,cAAc;AAClC,WAAO,OAAO;AAAA,EAChB;AACF;AAUO,SAAS,mBAAmB,QAAgB,UAAwB;AACzE,QAAM,YAAY,IAAI,IAAI,OAAO,KAAK,EAAE,MAAM,KAAK,EAAE,OAAO,OAAO,CAAC;AACpE,QAAM,cAAc,IAAI,IAAI,SAAS,KAAK,EAAE,MAAM,KAAK,EAAE,OAAO,OAAO,CAAC;AAExE,QAAM,UAAU,CAAC,GAAG,WAAW,EAAE,OAAO,CAAC,MAAM,CAAC,UAAU,IAAI,CAAC,CAAC;AAChE,QAAM,QAAQ,CAAC,GAAG,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY,IAAI,CAAC,CAAC;AAE9D,MAAI,QAAQ,SAAS,KAAK,MAAM,SAAS,GAAG;AAC1C,UAAM,QAAkB,CAAC;AACzB,QAAI,QAAQ,SAAS,EAAG,OAAM,KAAK,YAAY,QAAQ,KAAK,IAAI,CAAC,EAAE;AACnE,QAAI,MAAM,SAAS,EAAG,OAAM,KAAK,YAAY,MAAM,KAAK,IAAI,CAAC,EAAE;AAC/D,UAAM,IAAI,MAAM;AAAA,IAAsB,MAAM,KAAK,MAAM,CAAC,EAAE;AAAA,EAC5D;AACF;AAyBO,SAAS,oBACd,SACA,cAMM;AACN,MAAI,aAAa,aAAa,QAAW;AACvC,UAAM,SAAS,QAAQ,cAAc;AACrC,QAAI,SAAS,aAAa,UAAU;AAClC,YAAM,IAAI;AAAA,QACR,qCAAqC,aAAa,QAAQ,eAAe,MAAM;AAAA,MACjF;AAAA,IACF;AAAA,EACF;AAEA,MAAI,aAAa,mBAAmB,QAAW;AAC7C,UAAM,SAAS,QAAQ,eAAe;AACtC,QAAI,SAAS,aAAa,gBAAgB;AACxC,YAAM,IAAI;AAAA,QACR,8BAA8B,MAAM,uBAAkB,aAAa,cAAc;AAAA,MACnF;AAAA,IACF;AAAA,EACF;AAEA,MAAI,aAAa,qBAAqB,QAAW;AAC/C,UAAM,SAAS,QAAQ,iBAAiB;AACxC,QAAI,SAAS,aAAa,kBAAkB;AAC1C,YAAM,IAAI;AAAA,QACR,qCAAqC,aAAa,gBAAgB,wBAAwB,MAAM;AAAA,MAClG;AAAA,IACF;AAAA,EACF;AAEA,MAAI,aAAa,oBAAoB,QAAW;AAC9C,UAAM,OAAO,QAAQ,aAAa;AAClC,UAAM,QAAQ,QAAQ,QAAQ,eAAe;AAC7C,UAAM,OAAO,QAAQ,IAAI,OAAO,QAAQ;AACxC,QAAI,OAAO,aAAa,iBAAiB;AACvC,YAAM,IAAI;AAAA,QACR,mCAAmC,OAAO,KAAK,QAAQ,CAAC,CAAC,uBAAkB,aAAa,kBAAkB,KAAK,QAAQ,CAAC,CAAC;AAAA,MAC3H;AAAA,IACF;AAAA,EACF;AACF;AAQO,SAAS,oBAAoB,cAAyD;AAC3F,SAAO,CAAC,YAAmC;AACzC,QAAI;AACF,0BAAoB,SAAS,YAAY;AACzC,aAAO,EAAE,MAAM,MAAM,SAAS,MAAM,sCAAsC;AAAA,IAC5E,SAAS,GAAY;AACnB,YAAM,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;AACrD,aAAO,EAAE,MAAM,OAAO,SAAS,MAAM,IAAI;AAAA,IAC3C;AAAA,EACF;AACF;AAGO,IAAM,8BAA8B;AAAA,EACzC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;","names":[]}