coralite 0.41.0 → 0.43.0

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.
Files changed (35) hide show
  1. package/dist/lib/component-setup.d.ts.map +1 -1
  2. package/dist/lib/config.d.ts.map +1 -1
  3. package/dist/lib/coralite-element.d.ts +11 -0
  4. package/dist/lib/coralite-element.d.ts.map +1 -1
  5. package/dist/lib/coralite-element.js +50 -4
  6. package/dist/lib/coralite-element.js.map +2 -2
  7. package/dist/lib/coralite.d.ts +1 -1
  8. package/dist/lib/coralite.d.ts.map +1 -1
  9. package/dist/lib/hooks.d.ts +3 -1
  10. package/dist/lib/hooks.d.ts.map +1 -1
  11. package/dist/lib/index.js +430 -168
  12. package/dist/lib/index.js.map +2 -2
  13. package/dist/lib/plugin-setup.d.ts.map +1 -1
  14. package/dist/lib/plugin.js.map +1 -1
  15. package/dist/lib/renderer.d.ts.map +1 -1
  16. package/dist/lib/script-manager.d.ts.map +1 -1
  17. package/dist/lib/utils/client/inject.d.ts +2 -1
  18. package/dist/lib/utils/client/inject.d.ts.map +1 -1
  19. package/dist/lib/utils/client/inject.js +2 -2
  20. package/dist/lib/utils/client/inject.js.map +2 -2
  21. package/dist/lib/utils/client/runtime.d.ts +4 -4
  22. package/dist/lib/utils/client/runtime.d.ts.map +1 -1
  23. package/dist/lib/utils/core.js.map +1 -1
  24. package/dist/lib/utils/index.js +10 -11
  25. package/dist/lib/utils/index.js.map +2 -2
  26. package/dist/lib/utils/server/render.d.ts +1 -1
  27. package/dist/lib/utils/server/render.d.ts.map +1 -1
  28. package/dist/lib/utils/server/server.d.ts.map +1 -1
  29. package/dist/plugins/testing.d.ts.map +1 -1
  30. package/dist/types/core.d.ts +61 -2
  31. package/dist/types/core.d.ts.map +1 -1
  32. package/dist/types/script.d.ts +4 -0
  33. package/dist/types/script.d.ts.map +1 -1
  34. package/package.json +1 -1
  35. package/llms.txt +0 -488
package/dist/lib/index.js CHANGED
@@ -53,17 +53,16 @@ function defaultOnError({ level, message, error }) {
53
53
  }
54
54
  }
55
55
  function handleError({ onErrorCallback, data }) {
56
- const error = data.error;
57
- if (error && "isCoraliteError" in error && error.isCoraliteError) {
58
- const coraliteError = error;
59
- data.componentId = data.componentId || coraliteError.componentId;
60
- data.filePath = data.filePath || coraliteError.filePath;
61
- data.instanceId = data.instanceId || coraliteError.instanceId;
62
- data.pagePath = data.pagePath || coraliteError.pagePath;
63
- data.path = data.path || coraliteError.path;
64
- data.line = data.line || coraliteError.line;
65
- data.column = data.column || coraliteError.column;
66
- data.stackFile = data.stackFile || coraliteError.stackFile;
56
+ const { error } = data;
57
+ if (error && typeof error === "object" && "isCoraliteError" in error && error.isCoraliteError) {
58
+ data.componentId ??= error.componentId;
59
+ data.filePath ??= error.filePath;
60
+ data.instanceId ??= error.instanceId;
61
+ data.pagePath ??= error.pagePath;
62
+ data.path ??= error.path;
63
+ data.line ??= error.line;
64
+ data.column ??= error.column;
65
+ data.stackFile ??= error.stackFile;
67
66
  }
68
67
  if (onErrorCallback) {
69
68
  onErrorCallback(data);
@@ -2123,7 +2122,7 @@ function validateSerializable(value, path2 = "root", seen = /* @__PURE__ */ new
2123
2122
 
2124
2123
  // lib/utils/server/server.js
2125
2124
  import { parse as parseJS } from "acorn";
2126
- import { simple as walkJS } from "acorn-walk";
2125
+ import { simple as walkJS, ancestor as walkAncestorJS } from "acorn-walk";
2127
2126
  import render from "dom-serializer";
2128
2127
  import { sanitize } from "isomorphic-dompurify";
2129
2128
 
@@ -2189,6 +2188,46 @@ function getAST(code, locations = false) {
2189
2188
  astCache.set(cacheKey, ast);
2190
2189
  return ast;
2191
2190
  }
2191
+ function resolveStringValues(node, ancestors) {
2192
+ if (!node) {
2193
+ return [];
2194
+ }
2195
+ if (node.type === "Literal" && typeof node.value === "string") {
2196
+ return [node.value];
2197
+ }
2198
+ if (node.type === "ConditionalExpression") {
2199
+ return [
2200
+ ...resolveStringValues(node.consequent, ancestors),
2201
+ ...resolveStringValues(node.alternate, ancestors)
2202
+ ];
2203
+ }
2204
+ if (node.type === "Identifier" && ancestors && ancestors.length > 0) {
2205
+ const name = node.name;
2206
+ for (let i = ancestors.length - 1; i >= 0; i--) {
2207
+ const ancestor = ancestors[i];
2208
+ let body = null;
2209
+ if (ancestor.type === "BlockStatement" || ancestor.type === "Program") {
2210
+ body = ancestor.body;
2211
+ } else if (ancestor.type === "FunctionExpression" || ancestor.type === "ArrowFunctionExpression" || ancestor.type === "FunctionDeclaration") {
2212
+ if (ancestor.body && ancestor.body.type === "BlockStatement") {
2213
+ body = ancestor.body.body;
2214
+ }
2215
+ }
2216
+ if (body && Array.isArray(body)) {
2217
+ for (const stmt of body) {
2218
+ if (stmt.type === "VariableDeclaration") {
2219
+ for (const decl of stmt.declarations) {
2220
+ if (decl.id.type === "Identifier" && decl.id.name === name && decl.init) {
2221
+ return resolveStringValues(decl.init, []);
2222
+ }
2223
+ }
2224
+ }
2225
+ }
2226
+ }
2227
+ }
2228
+ }
2229
+ return [];
2230
+ }
2192
2231
  function extractFromHTMLString(html, components) {
2193
2232
  try {
2194
2233
  const matches = html.matchAll(/<([a-zA-Z0-9-]+)/g);
@@ -2205,7 +2244,7 @@ function findAndExtractScript(code) {
2205
2244
  const ast = getAST(code, true);
2206
2245
  let result = null;
2207
2246
  const components = /* @__PURE__ */ new Set();
2208
- const findHTMLComponents = (node) => {
2247
+ const findHTMLComponents = (node, ancestors = []) => {
2209
2248
  if (node.type === "Literal" && typeof node.value === "string") {
2210
2249
  extractFromHTMLString(node.value, components);
2211
2250
  } else if (node.type === "TemplateLiteral") {
@@ -2214,32 +2253,50 @@ function findAndExtractScript(code) {
2214
2253
  extractFromHTMLString(element.value.cooked, components);
2215
2254
  }
2216
2255
  }
2256
+ } else {
2257
+ const values = resolveStringValues(node, ancestors);
2258
+ for (const val of values) {
2259
+ extractFromHTMLString(val, components);
2260
+ }
2217
2261
  }
2218
2262
  };
2219
- walkJS(ast, {
2220
- AssignmentExpression(node) {
2263
+ walkAncestorJS(ast, {
2264
+ AssignmentExpression(node, ancestors) {
2221
2265
  if (node.left.type === "MemberExpression" && node.left.property.type === "Identifier" && (node.left.property.name === "innerHTML" || node.left.property.name === "outerHTML")) {
2222
- findHTMLComponents(node.right);
2266
+ findHTMLComponents(node.right, ancestors);
2223
2267
  }
2224
2268
  },
2225
- CallExpression(node) {
2269
+ CallExpression(node, ancestors) {
2226
2270
  if (node.callee && node.callee.type === "MemberExpression" && node.callee.object && node.callee.object.type === "Identifier" && node.callee.object.name === "document" && node.callee.property && node.callee.property.type === "Identifier" && node.callee.property.name === "createElement") {
2227
2271
  const arg = node.arguments[0];
2228
- if (arg && (arg.type !== "Literal" || typeof arg.value === "string" && arg.value.includes("-"))) {
2229
- if (arg.type === "Literal" && typeof arg.value === "string") {
2230
- components.add(arg.value);
2272
+ if (arg) {
2273
+ const values = resolveStringValues(arg, ancestors);
2274
+ for (const val of values) {
2275
+ if (val.includes("-")) {
2276
+ components.add(val);
2277
+ }
2231
2278
  }
2232
2279
  }
2233
2280
  }
2234
2281
  if (node.callee && node.callee.type === "MemberExpression" && node.callee.property && node.callee.property.type === "Identifier" && node.callee.property.name === "insertAdjacentHTML") {
2235
2282
  const arg = node.arguments[1];
2236
2283
  if (arg) {
2237
- findHTMLComponents(arg);
2284
+ findHTMLComponents(arg, ancestors);
2238
2285
  }
2239
2286
  }
2240
2287
  if (node.callee && node.callee.type === "Identifier" && node.callee.name === "defineComponent") {
2241
2288
  const firstArg = node.arguments[0];
2242
2289
  if (firstArg && firstArg.type === "ObjectExpression") {
2290
+ const depsProp = firstArg.properties.find(
2291
+ (prop) => prop.type === "Property" && prop.key && prop.key.type === "Identifier" && prop.key.name === "dependencies"
2292
+ );
2293
+ if (depsProp && depsProp.type === "Property" && depsProp.value.type === "ArrayExpression") {
2294
+ for (const el of depsProp.value.elements) {
2295
+ if (el && el.type === "Literal" && typeof el.value === "string") {
2296
+ components.add(el.value);
2297
+ }
2298
+ }
2299
+ }
2243
2300
  const scriptProp = firstArg.properties.find(
2244
2301
  (prop) => prop.type === "Property" && prop.key && prop.key.type === "Identifier" && prop.key.name === "client"
2245
2302
  );
@@ -2248,12 +2305,28 @@ function findAndExtractScript(code) {
2248
2305
  let startLine = value.loc.start.line - 1;
2249
2306
  let prefix = "";
2250
2307
  let content = "";
2308
+ let instanceIdVar;
2309
+ if (value.params && value.params[0]) {
2310
+ const param = value.params[0];
2311
+ if (param.type === "Identifier") {
2312
+ instanceIdVar = param.name + ".instanceId";
2313
+ } else if (param.type === "ObjectPattern") {
2314
+ const idProp = param.properties.find((p) => p.key?.type === "Identifier" && p.key?.name === "instanceId");
2315
+ if (idProp) {
2316
+ if (idProp.value.type === "Identifier") {
2317
+ instanceIdVar = idProp.value.name;
2318
+ }
2319
+ }
2320
+ }
2321
+ } else if (method) {
2322
+ instanceIdVar = "this._instanceId";
2323
+ }
2251
2324
  let source = code.slice(value.start, value.end);
2252
2325
  const replacements = [];
2253
- walkJS(value, {
2254
- AssignmentExpression(node2) {
2326
+ walkAncestorJS(value, {
2327
+ AssignmentExpression(node2, ancestorsInClient) {
2255
2328
  if (node2.left.type === "MemberExpression" && node2.left.property.type === "Identifier" && (node2.left.property.name === "innerHTML" || node2.left.property.name === "outerHTML")) {
2256
- findHTMLComponents(node2.right);
2329
+ findHTMLComponents(node2.right, [...ancestors, ...ancestorsInClient]);
2257
2330
  replacements.push({
2258
2331
  start: node2.right.start - value.start,
2259
2332
  end: node2.right.start - value.start,
@@ -2262,28 +2335,36 @@ function findAndExtractScript(code) {
2262
2335
  replacements.push({
2263
2336
  start: node2.right.end - value.start,
2264
2337
  end: node2.right.end - value.start,
2265
- replacement: ")"
2338
+ replacement: `, ${instanceIdVar})`
2266
2339
  });
2267
2340
  }
2268
2341
  },
2269
- CallExpression(node2) {
2342
+ CallExpression(node2, ancestorsInClient) {
2343
+ const combinedAncestors = [...ancestors, ...ancestorsInClient];
2270
2344
  if (node2.callee && node2.callee.type === "MemberExpression" && node2.callee.object && node2.callee.object.type === "Identifier" && node2.callee.object.name === "document" && node2.callee.property && node2.callee.property.type === "Identifier" && node2.callee.property.name === "createElement") {
2271
2345
  const arg = node2.arguments[0];
2272
- if (arg && (arg.type !== "Literal" || typeof arg.value === "string" && arg.value.includes("-"))) {
2273
- if (arg.type === "Literal" && typeof arg.value === "string") {
2274
- components.add(arg.value);
2346
+ if (arg) {
2347
+ const values = resolveStringValues(arg, combinedAncestors);
2348
+ let isCustom = false;
2349
+ for (const val of values) {
2350
+ if (val.includes("-")) {
2351
+ components.add(val);
2352
+ isCustom = true;
2353
+ }
2354
+ }
2355
+ if (isCustom || arg.type === "Literal" && typeof arg.value === "string" && arg.value.includes("-") || arg.type !== "Literal") {
2356
+ replacements.push({
2357
+ start: node2.callee.start - value.start,
2358
+ end: node2.callee.end - value.start,
2359
+ replacement: "createCoraliteElement"
2360
+ });
2275
2361
  }
2276
- replacements.push({
2277
- start: node2.callee.start - value.start,
2278
- end: node2.callee.end - value.start,
2279
- replacement: "createCoraliteElement"
2280
- });
2281
2362
  }
2282
2363
  }
2283
2364
  if (node2.callee && node2.callee.type === "MemberExpression" && node2.callee.property && node2.callee.property.type === "Identifier" && node2.callee.property.name === "insertAdjacentHTML") {
2284
2365
  const arg = node2.arguments[1];
2285
2366
  if (arg) {
2286
- findHTMLComponents(arg);
2367
+ findHTMLComponents(arg, combinedAncestors);
2287
2368
  replacements.push({
2288
2369
  start: arg.start - value.start,
2289
2370
  end: arg.start - value.start,
@@ -2292,7 +2373,7 @@ function findAndExtractScript(code) {
2292
2373
  replacements.push({
2293
2374
  start: arg.end - value.start,
2294
2375
  end: arg.end - value.start,
2295
- replacement: ")"
2376
+ replacement: `, ${instanceIdVar})`
2296
2377
  });
2297
2378
  }
2298
2379
  }
@@ -2324,8 +2405,13 @@ function findAndExtractScript(code) {
2324
2405
  }
2325
2406
  } else if (node.callee && node.callee.type === "Identifier" && node.callee.name === "createCoraliteElement") {
2326
2407
  const arg = node.arguments[0];
2327
- if (arg && arg.type === "Literal" && typeof arg.value === "string") {
2328
- components.add(arg.value);
2408
+ if (arg) {
2409
+ const values = resolveStringValues(arg, ancestors);
2410
+ for (const val of values) {
2411
+ if (val.includes("-")) {
2412
+ components.add(val);
2413
+ }
2414
+ }
2329
2415
  }
2330
2416
  }
2331
2417
  }
@@ -2391,32 +2477,42 @@ function findAndExtractImperativeComponents(code) {
2391
2477
  try {
2392
2478
  const ast = getAST(code);
2393
2479
  const components = /* @__PURE__ */ new Set();
2394
- const findHTMLComponents = (node) => {
2480
+ const findHTMLComponents = (node, ancestors = []) => {
2395
2481
  if (node.type === "Literal" && typeof node.value === "string") {
2396
2482
  extractFromHTMLString(node.value, components);
2397
2483
  } else if (node.type === "TemplateLiteral") {
2398
2484
  for (const element of node.quasis) {
2399
2485
  extractFromHTMLString(element.value.cooked, components);
2400
2486
  }
2487
+ } else {
2488
+ const values = resolveStringValues(node, ancestors);
2489
+ for (const val of values) {
2490
+ extractFromHTMLString(val, components);
2491
+ }
2401
2492
  }
2402
2493
  };
2403
- walkJS(ast, {
2404
- AssignmentExpression(node) {
2494
+ walkAncestorJS(ast, {
2495
+ AssignmentExpression(node, ancestors) {
2405
2496
  if (node.left.type === "MemberExpression" && node.left.property.type === "Identifier" && (node.left.property.name === "innerHTML" || node.left.property.name === "outerHTML")) {
2406
- findHTMLComponents(node.right);
2497
+ findHTMLComponents(node.right, ancestors);
2407
2498
  }
2408
2499
  },
2409
- CallExpression(node) {
2500
+ CallExpression(node, ancestors) {
2410
2501
  if (node.callee && (node.callee.type === "MemberExpression" && node.callee.object && node.callee.object.type === "Identifier" && node.callee.object.name === "document" && node.callee.property && node.callee.property.type === "Identifier" && node.callee.property.name === "createElement" || node.callee.type === "Identifier" && node.callee.name === "createCoraliteElement")) {
2411
2502
  const arg = node.arguments[0];
2412
- if (arg && arg.type === "Literal" && typeof arg.value === "string" && arg.value.includes("-")) {
2413
- components.add(arg.value);
2503
+ if (arg) {
2504
+ const values = resolveStringValues(arg, ancestors);
2505
+ for (const val of values) {
2506
+ if (val.includes("-")) {
2507
+ components.add(val);
2508
+ }
2509
+ }
2414
2510
  }
2415
2511
  }
2416
2512
  if (node.callee && node.callee.type === "MemberExpression" && node.callee.property && node.callee.property.type === "Identifier" && node.callee.property.name === "insertAdjacentHTML") {
2417
2513
  const arg = node.arguments[1];
2418
2514
  if (arg) {
2419
- findHTMLComponents(arg);
2515
+ findHTMLComponents(arg, ancestors);
2420
2516
  }
2421
2517
  }
2422
2518
  }
@@ -2685,9 +2781,19 @@ ScriptManager.prototype.compileAllInstances = async function(instances, mode) {
2685
2781
  return factories;
2686
2782
  })();
2687
2783
  `);
2784
+ const clientMocks = {};
2785
+ if (mode === "testing" && this.options.testing?.mocks?.plugins) {
2786
+ for (const [key, mock] of Object.entries(this.options.testing.mocks.plugins)) {
2787
+ if (mock.client) {
2788
+ clientMocks[key] = { client: mock.client };
2789
+ }
2790
+ }
2791
+ }
2792
+ const clientMocksStr = serialize(clientMocks);
2688
2793
  entryCodeParts.push(`const getClientContext = async (instanceContext) => {
2689
2794
  const factories = await resolvedContextFactoriesPromise;
2690
2795
  const cache = new Map();
2796
+ const clientMocks = ${clientMocksStr};
2691
2797
 
2692
2798
  return new Proxy(instanceContext, {
2693
2799
  get(target, prop, receiver) {
@@ -2702,7 +2808,16 @@ ScriptManager.prototype.compileAllInstances = async function(instances, mode) {
2702
2808
  throw new Error('Coralite Plugin Error: The plugin "' + String(prop) + '" must be a function for the second phase (instance context). Received: ' + typeof factory);
2703
2809
  }
2704
2810
 
2705
- const resolved = factory(receiver);
2811
+ let resolved = factory(receiver);
2812
+
2813
+ if (clientMocks[prop]?.client?.context) {
2814
+ if (typeof resolved === 'object' && resolved !== null) {
2815
+ resolved = Object.assign({}, resolved, clientMocks[prop].client.context);
2816
+ } else {
2817
+ resolved = clientMocks[prop].client.context;
2818
+ }
2819
+ }
2820
+
2706
2821
  cache.set(prop, resolved);
2707
2822
  return resolved;
2708
2823
  },
@@ -3460,24 +3575,37 @@ var staticAssetPlugin = (assets = []) => {
3460
3575
  };
3461
3576
 
3462
3577
  // plugins/testing.js
3463
- function traverseAndAddTestId(children, instanceId, { autoTestId = false, counters = {} } = {}) {
3578
+ function traverseAndAddTestId(children, instanceId, { autoTestId = false, counters = {}, mode = "production" } = {}) {
3464
3579
  if (!Array.isArray(children)) {
3465
3580
  return;
3466
3581
  }
3582
+ const isProduction = mode === "production";
3583
+ const isDevOrTest = mode === "development" || mode === "testing";
3584
+ let prefix = "";
3585
+ if (instanceId === "page") {
3586
+ prefix = "page__";
3587
+ } else if (instanceId) {
3588
+ prefix = `${instanceId}__`;
3589
+ }
3467
3590
  for (let i = 0; i < children.length; i++) {
3468
3591
  const node = children[i];
3469
3592
  if (node.type === "tag") {
3470
- if (node.attribs?.ref) {
3471
- if (!node.attribs["data-testid"]) {
3472
- const refValue = node.attribs.ref;
3473
- const prefix = instanceId ? `${instanceId}__` : "";
3474
- if (instanceId === "page" && refValue.includes("__")) {
3475
- node.attribs["data-testid"] = refValue;
3476
- } else {
3477
- node.attribs["data-testid"] = `${prefix}${refValue}`;
3593
+ if (node.attribs) {
3594
+ if (node.attribs.test !== void 0) {
3595
+ delete node.attribs.test;
3596
+ }
3597
+ if (node.attribs["data-testid"] !== void 0) {
3598
+ if (isProduction) {
3599
+ delete node.attribs["data-testid"];
3600
+ } else if (isDevOrTest) {
3601
+ const val = node.attribs["data-testid"];
3602
+ if (prefix && !val.startsWith(prefix)) {
3603
+ node.attribs["data-testid"] = `${prefix}${val}`;
3604
+ }
3478
3605
  }
3479
3606
  }
3480
- } else if (autoTestId && instanceId) {
3607
+ }
3608
+ if (autoTestId && isDevOrTest) {
3481
3609
  const tagName = node.name.toLowerCase();
3482
3610
  const isInteractive = [
3483
3611
  "button",
@@ -3496,7 +3624,7 @@ function traverseAndAddTestId(children, instanceId, { autoTestId = false, counte
3496
3624
  node.attribs = {};
3497
3625
  }
3498
3626
  if (!node.attribs["data-testid"]) {
3499
- node.attribs["data-testid"] = `${instanceId}__${tagName}-${index}`;
3627
+ node.attribs["data-testid"] = `${prefix}${tagName}-${index}`;
3500
3628
  }
3501
3629
  }
3502
3630
  }
@@ -3504,7 +3632,8 @@ function traverseAndAddTestId(children, instanceId, { autoTestId = false, counte
3504
3632
  if (node.children?.length > 0) {
3505
3633
  traverseAndAddTestId(node.children, instanceId, {
3506
3634
  autoTestId,
3507
- counters
3635
+ counters,
3636
+ mode
3508
3637
  });
3509
3638
  }
3510
3639
  }
@@ -3527,43 +3656,51 @@ var testingPlugin = definePlugin({
3527
3656
  app.options.externalStyles.push(`data:text/css;base64,${Buffer.from(velocityStyle).toString("base64")}`);
3528
3657
  },
3529
3658
  onBeforeComponentRender: ({ instanceId, template, app }) => {
3530
- const isTesting = app.options.mode === "testing";
3659
+ const mode = app.options.mode;
3660
+ const isDevOrTest = mode === "development" || mode === "testing";
3531
3661
  const counters = {};
3532
3662
  const templateNode = template;
3533
3663
  if (templateNode && templateNode.children) {
3534
3664
  traverseAndAddTestId(templateNode.children, instanceId, {
3535
- autoTestId: isTesting,
3536
- counters
3665
+ autoTestId: isDevOrTest,
3666
+ counters,
3667
+ mode
3537
3668
  });
3538
3669
  }
3539
3670
  },
3671
+ onAfterComponentRender: ({ result, app }) => {
3672
+ const mode = app.options.mode;
3673
+ if (mode !== "production") {
3674
+ return;
3675
+ }
3676
+ const traverse = (children) => {
3677
+ if (!Array.isArray(children)) {
3678
+ return;
3679
+ }
3680
+ for (const node of children) {
3681
+ if (node.type === "tag" && node.attribs) {
3682
+ delete node.attribs["data-testid"];
3683
+ delete node.attribs.test;
3684
+ }
3685
+ if (node.children) {
3686
+ traverse(node.children);
3687
+ }
3688
+ }
3689
+ };
3690
+ if (result && result.children) {
3691
+ traverse(result.children);
3692
+ }
3693
+ },
3540
3694
  onPageSet: ({ elements, app }) => {
3541
- const isTesting = app.options.mode === "testing";
3695
+ const mode = app.options.mode;
3696
+ const isDevOrTest = mode === "development" || mode === "testing";
3542
3697
  const counters = {};
3543
3698
  traverseAndAddTestId(elements?.root?.children, "page", {
3544
- autoTestId: isTesting,
3545
- counters
3699
+ autoTestId: isDevOrTest,
3700
+ counters,
3701
+ mode
3546
3702
  });
3547
3703
  }
3548
- },
3549
- client: {
3550
- onBeforeComponentRender: ({ instanceId, refs }) => {
3551
- for (let i = 0; i < refs.length; i++) {
3552
- const ref = refs[i];
3553
- const prefix = `${instanceId}__`;
3554
- const uniqueRefValue = ref.element && ref.element.getAttribute("ref");
3555
- if (ref.element && ref.element.setAttribute) {
3556
- const currentTestId = ref.element.getAttribute("data-testid");
3557
- if (!currentTestId || currentTestId === ref.name) {
3558
- if (uniqueRefValue && uniqueRefValue.startsWith(prefix)) {
3559
- ref.element.setAttribute("data-testid", uniqueRefValue);
3560
- } else {
3561
- ref.element.setAttribute("data-testid", `${prefix}${ref.name}`);
3562
- }
3563
- }
3564
- }
3565
- }
3566
- }
3567
3704
  }
3568
3705
  });
3569
3706
 
@@ -4062,7 +4199,7 @@ async function triggerPluginHook({ app, hooks, serverGlobalContext, name, initia
4062
4199
  }
4063
4200
  return currentData;
4064
4201
  }
4065
- function bindPlugins({ pluginFactories, instanceContext = {} }) {
4202
+ function bindPlugins({ pluginFactories, instanceContext = {}, app }) {
4066
4203
  const cache = /* @__PURE__ */ new Map();
4067
4204
  const proxy = new Proxy(instanceContext, {
4068
4205
  get(target, prop) {
@@ -4082,7 +4219,17 @@ function bindPlugins({ pluginFactories, instanceContext = {} }) {
4082
4219
  if (typeof factory !== "function") {
4083
4220
  throw new CoraliteError(`Coralite Plugin Error: The plugin "${String(prop)}" must be a function for the second phase (instance context). Received: ${typeof factory}`);
4084
4221
  }
4085
- const resolved = factory(proxy);
4222
+ let resolved = factory(proxy);
4223
+ if (app?.options?.mode === "testing" && app.options.testing?.mocks?.plugins) {
4224
+ const pluginMock = app.options.testing.mocks.plugins[prop];
4225
+ if (pluginMock?.server?.context) {
4226
+ if (typeof resolved === "object" && resolved !== null) {
4227
+ resolved = Object.assign({}, resolved, pluginMock.server.context);
4228
+ } else {
4229
+ resolved = pluginMock.server.context;
4230
+ }
4231
+ }
4232
+ }
4086
4233
  cache.set(prop, resolved);
4087
4234
  return resolved;
4088
4235
  },
@@ -4238,8 +4385,8 @@ function createComponentDefinition({ app }) {
4238
4385
  }
4239
4386
  }
4240
4387
  let serverToExecute = server;
4241
- if (app.options.mode === "testing" && app.options.testing?.mocks) {
4242
- const mock = app.options.testing.mocks[module.id];
4388
+ if (app.options.mode === "testing" && app.options.testing?.mocks?.components) {
4389
+ const mock = app.options.testing.mocks.components[module.id];
4243
4390
  if (mock && typeof mock.server === "function") {
4244
4391
  serverToExecute = mock.server;
4245
4392
  }
@@ -4258,7 +4405,9 @@ function createComponentDefinition({ app }) {
4258
4405
  }
4259
4406
  state.__script__.server = serverResult;
4260
4407
  Object.assign(state, serverResult);
4261
- Object.assign(state.__script__.defaultValues, serverResult);
4408
+ if (state.__script__.defaultValues) {
4409
+ Object.assign(state.__script__.defaultValues, serverResult);
4410
+ }
4262
4411
  }
4263
4412
  }
4264
4413
  if (getters) {
@@ -4513,6 +4662,15 @@ async function setupPlugins({
4513
4662
  }) {
4514
4663
  const pluginsToInit = app.options.plugins;
4515
4664
  const allExportNames = /* @__PURE__ */ new Set();
4665
+ if (app.options.mode === "testing" && app.options.testing?.mocks?.plugins) {
4666
+ const mockKeys = Object.keys(app.options.testing.mocks.plugins);
4667
+ const registeredPluginNames = pluginsToInit.map((p) => p.server?.name || p.name);
4668
+ for (const mockKey of mockKeys) {
4669
+ if (!registeredPluginNames.includes(mockKey)) {
4670
+ console.warn(`[Coralite Warning]: Mock defined for plugin "${mockKey}", but no plugin with that name is registered.`);
4671
+ }
4672
+ }
4673
+ }
4516
4674
  for (const plugin of pluginsToInit) {
4517
4675
  if (plugin.server) {
4518
4676
  const serverName = plugin.server.name || plugin.name;
@@ -4932,6 +5090,9 @@ function injectReadinessScript(root, head, hasScripts, mode) {
4932
5090
  children: []
4933
5091
  });
4934
5092
  let data = "class CoraliteLifecycleManager { constructor() { this.defined = new Promise(r => this._dr = r); this.rendered = new Promise(r => this._rr = r); this.hydrated = new Promise(r => this._hr = r); this._t = 0; this._rc = 0; this._hc = 0; this._ts = 0; this._dt = new Set(); this._ip = new WeakMap(); this._ir = new WeakMap(); this._rs = new WeakSet(); this._hs = new WeakSet(); this._s = false; } _start(t, ts) { this._t = t; this._ts = ts; this._s = true; this._check(); } _check() { if (!this._s) return; if (this._rc >= this._t) this._rr(); if (this._hc >= this._t) this._hr(); if (this._dt.size >= this._ts) this._dr(); } _markDefined(tag) { this._dt.add(tag); this._check(); } _markInstanceRendered(el) { if (el.hasAttribute('data-coralite-initial') && !this._rs.has(el)) { this._rs.add(el); this._rc++; this._check(); } } _markInstanceReady(el) { const r = this._ir.get(el); r && r(); this._ip.set(el, Promise.resolve()); if (el.hasAttribute('data-coralite-initial') && !this._hs.has(el)) { this._hs.add(el); this._hc++; this._check(); } } waitFor(el) { let p = this._ip.get(el); if (!p) { p = new Promise(r => this._ir.set(el, r)); this._ip.set(el, p); } return p; } } window.__coralite__ = { lifecycle: new CoraliteLifecycleManager() };";
5093
+ if (mode) {
5094
+ data += ` window.__coralite__.mode = '${mode}';`;
5095
+ }
4935
5096
  if (mode === "testing") {
4936
5097
  data += " window.__coralite__.components = {}; window.__coralite__.events = [];";
4937
5098
  }
@@ -4949,8 +5110,12 @@ function injectReadinessScript(root, head, hasScripts, mode) {
4949
5110
  root.children.unshift(readinessScriptElement);
4950
5111
  }
4951
5112
  }
4952
- function injectImportMap(root, head, importMap) {
4953
- if (!importMap || Object.keys(importMap).length === 0) {
5113
+ function injectImportMap(root, head, importMap, base) {
5114
+ const finalImportMap = { ...importMap };
5115
+ if (base) {
5116
+ finalImportMap["assets/js/manifest.js"] = `${base}assets/js/manifest.js`;
5117
+ }
5118
+ if (Object.keys(finalImportMap).length === 0) {
4954
5119
  return;
4955
5120
  }
4956
5121
  const importMapElement = createCoraliteElement({
@@ -4964,7 +5129,7 @@ function injectImportMap(root, head, importMap) {
4964
5129
  });
4965
5130
  importMapElement.children.push(createCoraliteTextNode({
4966
5131
  type: "text",
4967
- data: JSON.stringify({ imports: importMap }),
5132
+ data: JSON.stringify({ imports: finalImportMap }),
4968
5133
  parent: importMapElement
4969
5134
  }));
4970
5135
  if (head) {
@@ -5031,16 +5196,19 @@ function resolvePageQueue(pagesCollection, path2) {
5031
5196
  function generateClientRuntime({
5032
5197
  base,
5033
5198
  sharedChunkPath,
5034
- chunkManifest,
5035
5199
  declarativeTags = [],
5036
- hydrationData = "{}"
5200
+ hydrationData = "{}",
5201
+ mode = "production"
5037
5202
  }) {
5038
5203
  return `
5039
5204
  import { getClientContext, createCoraliteClass, globalClientHooks } from '${base}assets/js/${sharedChunkPath}';
5205
+ import componentManifest from '${base}assets/js/manifest.js';
5040
5206
 
5041
5207
  (async () => {
5042
5208
  const hydrationData = ${hydrationData};
5043
5209
  const declarativeTags = ${JSON.stringify(declarativeTags)};
5210
+ window.__coralite__ = window.__coralite__ || {};
5211
+ window.__coralite__.mode = '${mode}';
5044
5212
 
5045
5213
  const initialElements = Array.from(document.querySelectorAll('[data-cid]'))
5046
5214
  .filter(el => {
@@ -5073,7 +5241,6 @@ import { getClientContext, createCoraliteClass, globalClientHooks } from '${base
5073
5241
  globalThis.executableScripts = [];
5074
5242
  globalThis.globalAbortController = new AbortController();
5075
5243
 
5076
- const componentManifest = ${JSON.stringify(chunkManifest)};
5077
5244
  const loadCache = {};
5078
5245
 
5079
5246
  const loadComponent = (componentId) => {
@@ -5088,7 +5255,10 @@ import { getClientContext, createCoraliteClass, globalClientHooks } from '${base
5088
5255
 
5089
5256
  if (cssPath) {
5090
5257
  const fullCssPath = '${base}assets/css/' + cssPath;
5091
- if (!document.querySelector('link[href="' + fullCssPath + '"]')) {
5258
+ const inlineStyles = document.getElementById('coralite-inline-styles');
5259
+ const hasStyleInInline = inlineStyles && inlineStyles.textContent.includes('[data-style-selector="' + componentId + '"]');
5260
+
5261
+ if (!document.querySelector('link[href="' + fullCssPath + '"]') && !hasStyleInInline) {
5092
5262
  const link = document.createElement('link');
5093
5263
  link.rel = 'stylesheet';
5094
5264
  link.href = fullCssPath;
@@ -5153,8 +5323,38 @@ import { getClientContext, createCoraliteClass, globalClientHooks } from '${base
5153
5323
  return element;
5154
5324
  };
5155
5325
 
5156
- window.processHTML = (html) => {
5326
+ window.processHTML = (html, instanceId) => {
5157
5327
  if (typeof html !== 'string') return html;
5328
+
5329
+ const mode = (window.__coralite__ && window.__coralite__.mode) || 'production';
5330
+ const isDevOrTest = mode === 'development' || mode === 'testing';
5331
+ const isProduction = mode === 'production';
5332
+
5333
+ if (isDevOrTest || isProduction) {
5334
+ html = html.replace(/<([a-zA-Z0-9-]+)([^>]*)>/g, (match, tagName, attrs) => {
5335
+ let newAttrs = attrs;
5336
+
5337
+ // Strip deprecated 'test' attribute
5338
+ newAttrs = newAttrs.replace(/\\s+test\\s*=\\s*(['"]).*?\\1/g, '');
5339
+
5340
+ // Handle data-testid
5341
+ const testIdRegex = /\\s+data-testid\\s*=\\s*(['"])(.*?)\\1/g;
5342
+ if (isProduction) {
5343
+ newAttrs = newAttrs.replace(testIdRegex, '');
5344
+ } else if (isDevOrTest) {
5345
+ const prefix = instanceId ? instanceId + '__' : '';
5346
+ if (prefix) {
5347
+ newAttrs = newAttrs.replace(testIdRegex, (attrMatch, quote, testValue) => {
5348
+ if (testValue.startsWith(prefix)) return attrMatch;
5349
+ return ' data-testid="' + prefix + testValue + '"';
5350
+ });
5351
+ }
5352
+ }
5353
+
5354
+ return '<' + tagName + newAttrs + '>';
5355
+ });
5356
+ }
5357
+
5158
5358
  const matches = html.matchAll(/<([a-zA-Z0-9-]+)/g);
5159
5359
  for (const match of matches) {
5160
5360
  const tag = match[1].toLowerCase();
@@ -6029,27 +6229,7 @@ function createRenderer({
6029
6229
  injectExternalStyles(mappedComponent.root, headElement, normalizedOptions.externalStyles);
6030
6230
  }
6031
6231
  if (mappedSessionObject.styles.size > 0) {
6032
- if (isProduction && globalScriptResult && globalScriptResult.manifest) {
6033
- const cssPaths = [];
6034
- const remainingStyles = /* @__PURE__ */ new Map();
6035
- for (const [selector, css] of mappedSessionObject.styles) {
6036
- const entry = globalScriptResult.manifest[selector];
6037
- if (entry && entry.css) {
6038
- cssPaths.push(entry.css);
6039
- } else {
6040
- remainingStyles.set(selector, css);
6041
- }
6042
- }
6043
- if (cssPaths.length > 0) {
6044
- const base = normalizedOptions.baseURL.endsWith("/") ? normalizedOptions.baseURL : normalizedOptions.baseURL + "/";
6045
- injectExternalStyleLinks(mappedComponent.root, headElement, cssPaths, base);
6046
- }
6047
- if (remainingStyles.size > 0) {
6048
- injectStyles(mappedComponent.root, headElement, remainingStyles);
6049
- }
6050
- } else {
6051
- injectStyles(mappedComponent.root, headElement, mappedSessionObject.styles);
6052
- }
6232
+ injectStyles(mappedComponent.root, headElement, mappedSessionObject.styles);
6053
6233
  }
6054
6234
  if (componentsToInclude.size > 0) {
6055
6235
  const targetElement = headElement || bodyElement || mappedComponent.root;
@@ -6095,20 +6275,9 @@ function createRenderer({
6095
6275
  error: new Error(JSON.stringify(scriptResult.manifest))
6096
6276
  });
6097
6277
  }
6098
- const chunkManifest = {};
6099
- for (const tag of componentsToInclude) {
6100
- if (scriptResult.manifest[tag]) {
6101
- chunkManifest[tag] = scriptResult.manifest[tag];
6102
- }
6103
- }
6104
- for (const tag in scriptResult.manifest) {
6105
- if (tag !== "coralite-runtime" && !chunkManifest[tag]) {
6106
- chunkManifest[tag] = scriptResult.manifest[tag];
6107
- }
6108
- }
6109
6278
  injectReadinessScript(mappedComponent.root, headElement, true, normalizedOptions.mode);
6110
- injectImportMap(mappedComponent.root, headElement, scriptResult.importMap);
6111
6279
  const base = normalizedOptions.baseURL.endsWith("/") ? normalizedOptions.baseURL : normalizedOptions.baseURL + "/";
6280
+ injectImportMap(mappedComponent.root, headElement, scriptResult.importMap, base);
6112
6281
  const hydrationData = {};
6113
6282
  for (const [id, instance] of Object.entries(instances)) {
6114
6283
  if (instance.state && Object.keys(instance.state).length > 0) {
@@ -6119,9 +6288,9 @@ function createRenderer({
6119
6288
  const scriptContent = generateClientRuntime({
6120
6289
  base,
6121
6290
  sharedChunkPath: scriptResult.manifest["coralite-runtime"],
6122
- chunkManifest,
6123
6291
  declarativeTags: Array.from(declarativeTags),
6124
- hydrationData: serialize2(hydrationData)
6292
+ hydrationData: serialize2(hydrationData),
6293
+ mode: normalizedOptions.mode
6125
6294
  });
6126
6295
  const scriptElement = createCoraliteElement({
6127
6296
  type: "tag",
@@ -6240,10 +6409,74 @@ function createRenderer({
6240
6409
  if (!buildOptions) {
6241
6410
  buildOptions = {};
6242
6411
  }
6412
+ const projectRoot = app.options.projectRoot || process.cwd();
6413
+ const cacheDir = join4(projectRoot, ".coralite");
6414
+ const manifestPath = join4(cacheDir, "manifest.json");
6415
+ let manifest = {
6416
+ physical: {},
6417
+ virtual: {},
6418
+ dependencies: {},
6419
+ components: {}
6420
+ };
6421
+ try {
6422
+ const content = await readFile3(manifestPath, "utf8");
6423
+ manifest = JSON.parse(content);
6424
+ if (!manifest.components) {
6425
+ manifest.components = {};
6426
+ }
6427
+ } catch (e) {
6428
+ if (e.code !== "ENOENT") {
6429
+ handleError2({
6430
+ level: "WARN",
6431
+ message: `Could not parse manifest at ${manifestPath}: ${e.message}. Starting with fresh manifest.`
6432
+ });
6433
+ }
6434
+ }
6435
+ let componentBuildInfo = {
6436
+ completed: 0,
6437
+ skipped: 0,
6438
+ details: []
6439
+ };
6243
6440
  if (normalizedOptions.mode === "production" || normalizedOptions.mode === "testing") {
6244
6441
  const allComponentIds = app.components.list.map((c) => c.result.id);
6245
6442
  globalScriptResult = await scriptManager.compileAllInstances(allComponentIds, normalizedOptions.mode);
6246
6443
  Object.assign(outputFiles, globalScriptResult.outputFiles);
6444
+ if (globalScriptResult.manifest) {
6445
+ const manifestJS = `export default ${JSON.stringify(globalScriptResult.manifest)};`;
6446
+ outputFiles["manifest.js"] = {
6447
+ path: "assets/js/manifest.js",
6448
+ hashedPath: "manifest.js",
6449
+ text: manifestJS
6450
+ };
6451
+ const newComponentManifest = globalScriptResult.manifest;
6452
+ const oldComponentManifest = manifest.components;
6453
+ for (const [id, value] of Object.entries(newComponentManifest)) {
6454
+ if (id === "coralite-runtime") {
6455
+ continue;
6456
+ }
6457
+ const isNew = !oldComponentManifest[id];
6458
+ const hasChanged = !isNew && (value.js !== oldComponentManifest[id].js || value.css !== oldComponentManifest[id].css);
6459
+ if (isNew || hasChanged) {
6460
+ componentBuildInfo.completed++;
6461
+ componentBuildInfo.details.push({
6462
+ id,
6463
+ status: "built",
6464
+ reason: isNew ? "New component" : "Source changed"
6465
+ });
6466
+ } else {
6467
+ componentBuildInfo.skipped++;
6468
+ componentBuildInfo.details.push({
6469
+ id,
6470
+ status: "skipped"
6471
+ });
6472
+ }
6473
+ }
6474
+ if (typeof buildOptions.onComponentBuild === "function") {
6475
+ await buildOptions.onComponentBuild(componentBuildInfo);
6476
+ } else if (typeof normalizedOptions.onComponentBuild === "function") {
6477
+ await normalizedOptions.onComponentBuild(componentBuildInfo);
6478
+ }
6479
+ }
6247
6480
  } else if (normalizedOptions.mode === "development") {
6248
6481
  if (!siteWideBundlePromise) {
6249
6482
  const bundlePromise = (async () => {
@@ -6255,6 +6488,14 @@ function createRenderer({
6255
6488
  delete outputFiles[key];
6256
6489
  }
6257
6490
  Object.assign(outputFiles, result.outputFiles);
6491
+ if (result.manifest) {
6492
+ const manifestJS = `export default ${JSON.stringify(result.manifest)};`;
6493
+ outputFiles["manifest.js"] = {
6494
+ path: "assets/js/manifest.js",
6495
+ hashedPath: "manifest.js",
6496
+ text: manifestJS
6497
+ };
6498
+ }
6258
6499
  }
6259
6500
  return result;
6260
6501
  })();
@@ -6301,36 +6542,17 @@ function createRenderer({
6301
6542
  }
6302
6543
  }
6303
6544
  sealedQueues.add(buildId);
6304
- const projectRoot = app.options.projectRoot || process.cwd();
6305
- const cacheDir = join4(projectRoot, ".coralite");
6306
- const manifestPath = join4(cacheDir, "manifest.json");
6307
- let manifest = {
6308
- physical: {},
6309
- virtual: {},
6310
- dependencies: {}
6311
- };
6312
- try {
6313
- const content = await readFile3(manifestPath, "utf8");
6314
- manifest = JSON.parse(content);
6315
- for (const [path2, metadata] of Object.entries(manifest.physical || {})) {
6316
- if (metadata.dependencies) {
6317
- app._dependencyGraph.directPageComponents[path2] = metadata.dependencies;
6318
- }
6545
+ for (const [path2, metadata] of Object.entries(manifest.physical || {})) {
6546
+ if (metadata.dependencies) {
6547
+ app._dependencyGraph.directPageComponents[path2] = metadata.dependencies;
6319
6548
  }
6320
- for (const [path2, metadata] of Object.entries(manifest.virtual || {})) {
6321
- if (metadata.dependencies) {
6322
- app._dependencyGraph.directPageComponents[path2] = metadata.dependencies;
6323
- }
6324
- }
6325
- app._refreshDependencyGraph();
6326
- } catch (e) {
6327
- if (e.code !== "ENOENT") {
6328
- handleError2({
6329
- level: "WARN",
6330
- message: `Could not parse manifest at ${manifestPath}: ${e.message}. Starting with fresh manifest.`
6331
- });
6549
+ }
6550
+ for (const [path2, metadata] of Object.entries(manifest.virtual || {})) {
6551
+ if (metadata.dependencies) {
6552
+ app._dependencyGraph.directPageComponents[path2] = metadata.dependencies;
6332
6553
  }
6333
6554
  }
6555
+ app._refreshDependencyGraph();
6334
6556
  const mocksStr = app.options.testing?.mocks ? serialize2(app.options.testing.mocks) : "";
6335
6557
  const mocksHash = hash(mocksStr);
6336
6558
  const mocksChanged = manifest.testingMocksHash !== mocksHash;
@@ -6340,6 +6562,7 @@ function createRenderer({
6340
6562
  physical: {},
6341
6563
  virtual: {},
6342
6564
  dependencies: {},
6565
+ components: {},
6343
6566
  testingMocksHash: mocksHash
6344
6567
  };
6345
6568
  const componentChanges = /* @__PURE__ */ new Map();
@@ -6516,6 +6739,7 @@ function createRenderer({
6516
6739
  }
6517
6740
  try {
6518
6741
  await mkdir2(cacheDir, { recursive: true });
6742
+ newManifest.components = globalScriptResult?.manifest || {};
6519
6743
  const tempManifestPath = `${manifestPath}.tmp`;
6520
6744
  await writeFile(tempManifestPath, JSON.stringify(newManifest, null, 2));
6521
6745
  await rename(tempManifestPath, manifestPath);
@@ -6582,6 +6806,7 @@ async function createCoralite({
6582
6806
  ignoreByAttribute,
6583
6807
  skipRenderByAttribute,
6584
6808
  onError,
6809
+ onComponentBuild,
6585
6810
  mode = "production",
6586
6811
  output,
6587
6812
  testing
@@ -6621,6 +6846,7 @@ async function createCoralite({
6621
6846
  mode,
6622
6847
  path: path2,
6623
6848
  output: output ? normalize(output) : void 0,
6849
+ onComponentBuild,
6624
6850
  testing
6625
6851
  };
6626
6852
  const trackedOutputFiles = /* @__PURE__ */ new Set();
@@ -6747,7 +6973,8 @@ async function createCoralite({
6747
6973
  });
6748
6974
  const _bindPluginsLocal = (pluginFactories, instanceContext) => bindPlugins({
6749
6975
  pluginFactories,
6750
- instanceContext
6976
+ instanceContext,
6977
+ app
6751
6978
  });
6752
6979
  const _defineComponent = createComponentDefinition({ app });
6753
6980
  const _evaluateLocal = (options) => evaluate({
@@ -6872,9 +7099,7 @@ async function createCoralite({
6872
7099
  return results;
6873
7100
  }
6874
7101
  });
6875
- if (app.options.mode === "testing") {
6876
- app.options.plugins.unshift(testingPlugin);
6877
- }
7102
+ app.options.plugins.unshift(testingPlugin);
6878
7103
  app.options.plugins.unshift(metadataPlugin);
6879
7104
  if (assets) {
6880
7105
  app.options.plugins.unshift(staticAssetPlugin(assets));
@@ -6985,12 +7210,49 @@ function defineConfig(options) {
6985
7210
  if (typeof options.testing.mocks !== "object" || options.testing.mocks === null) {
6986
7211
  throw new CoraliteError('Config property "testing.mocks" must be an object');
6987
7212
  }
6988
- for (const [key, mock] of Object.entries(options.testing.mocks)) {
6989
- if (typeof mock !== "object" || mock === null) {
6990
- throw new CoraliteError(`Mock for component "${key}" must be an object`);
7213
+ const validMocksKeys = ["components", "plugins"];
7214
+ for (const key of Object.keys(options.testing.mocks)) {
7215
+ if (!validMocksKeys.includes(key)) {
7216
+ throw new CoraliteError(`Invalid key "${key}" in testing.mocks. Valid keys are: ${validMocksKeys.join(", ")}`);
7217
+ }
7218
+ }
7219
+ if (options.testing.mocks.components !== void 0) {
7220
+ if (typeof options.testing.mocks.components !== "object" || options.testing.mocks.components === null) {
7221
+ throw new CoraliteError('Config property "testing.mocks.components" must be an object');
6991
7222
  }
6992
- if (mock.server !== void 0 && typeof mock.server !== "function") {
6993
- throw new CoraliteError(`Mock server for component "${key}" must be a function`);
7223
+ for (const [key, mock] of Object.entries(options.testing.mocks.components)) {
7224
+ if (typeof mock !== "object" || mock === null) {
7225
+ throw new CoraliteError(`Mock for component "${key}" must be an object`);
7226
+ }
7227
+ if (mock.server !== void 0 && typeof mock.server !== "function") {
7228
+ throw new CoraliteError(`Mock server for component "${key}" must be a function`);
7229
+ }
7230
+ }
7231
+ }
7232
+ if (options.testing.mocks.plugins !== void 0) {
7233
+ if (typeof options.testing.mocks.plugins !== "object" || options.testing.mocks.plugins === null) {
7234
+ throw new CoraliteError('Config property "testing.mocks.plugins" must be an object');
7235
+ }
7236
+ for (const [key, mock] of Object.entries(options.testing.mocks.plugins)) {
7237
+ if (typeof mock !== "object" || mock === null) {
7238
+ throw new CoraliteError(`Mock for plugin "${key}" must be an object`);
7239
+ }
7240
+ if (mock.server !== void 0) {
7241
+ if (typeof mock.server !== "object" || mock.server === null) {
7242
+ throw new CoraliteError(`Mock server for plugin "${key}" must be an object`);
7243
+ }
7244
+ if (mock.server.context !== void 0 && (typeof mock.server.context !== "object" || mock.server.context === null)) {
7245
+ throw new CoraliteError(`Mock server context for plugin "${key}" must be an object`);
7246
+ }
7247
+ }
7248
+ if (mock.client !== void 0) {
7249
+ if (typeof mock.client !== "object" || mock.client === null) {
7250
+ throw new CoraliteError(`Mock client for plugin "${key}" must be an object`);
7251
+ }
7252
+ if (mock.client.context !== void 0 && (typeof mock.client.context !== "object" || mock.client.context === null)) {
7253
+ throw new CoraliteError(`Mock client context for plugin "${key}" must be an object`);
7254
+ }
7255
+ }
6994
7256
  }
6995
7257
  }
6996
7258
  }