@testsmith/api-spector 0.0.4 → 0.0.7

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.
@@ -27,6 +27,8 @@ const crypto = require("crypto");
27
27
  const vm = require("vm");
28
28
  const dayjs = require("dayjs");
29
29
  const tv4 = require("tv4");
30
+ const jsonpathPlus = require("jsonpath-plus");
31
+ const xmldom = require("@xmldom/xmldom");
30
32
  function _interopNamespaceDefault(e) {
31
33
  const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
32
34
  if (e) {
@@ -173,6 +175,43 @@ async function buildEnvVars(environment) {
173
175
  function mergeVars(envVars, collectionVars, globals2, localVars = {}) {
174
176
  return { ...globals2, ...collectionVars, ...envVars, ...localVars };
175
177
  }
178
+ function xmlFindAll(node, tag, nth) {
179
+ const results = [];
180
+ const siblings = Array.from(node.childNodes).filter((c) => c.nodeType === 1 && c.tagName === tag);
181
+ if (siblings[nth]) results.push(siblings[nth]);
182
+ for (const child of Array.from(node.childNodes)) {
183
+ if (child.nodeType === 1) results.push(...xmlFindAll(child, tag, nth));
184
+ }
185
+ return results;
186
+ }
187
+ function xmlQuerySelector(root, selector) {
188
+ const parts = selector.trim().split(/\s*>\s*/);
189
+ const m0 = parts[0].match(/^([A-Za-z0-9_:.-]+?)(?::nth-of-type\((\d+)\))?$/);
190
+ if (!m0) return null;
191
+ let candidates = xmlFindAll(root, m0[1], m0[2] ? parseInt(m0[2]) - 1 : 0);
192
+ for (let i = 1; i < parts.length; i++) {
193
+ const m = parts[i].match(/^([A-Za-z0-9_:.-]+?)(?::nth-of-type\((\d+)\))?$/);
194
+ if (!m) return null;
195
+ const tag = m[1];
196
+ const idx = m[2] ? parseInt(m[2]) - 1 : 0;
197
+ const next = [];
198
+ for (const node of candidates) {
199
+ const children = Array.from(node.childNodes).filter((c) => c.nodeType === 1 && c.tagName === tag);
200
+ if (children[idx]) next.push(children[idx]);
201
+ }
202
+ candidates = next;
203
+ }
204
+ return candidates[0] ?? null;
205
+ }
206
+ function makeSandboxDOMParser() {
207
+ return {
208
+ parseFromString(str, mime) {
209
+ const doc = new xmldom.DOMParser().parseFromString(str, mime);
210
+ doc.querySelector = (sel) => xmlQuerySelector(doc, sel);
211
+ return doc;
212
+ }
213
+ };
214
+ }
176
215
  let _fakerCache = null;
177
216
  async function getFaker() {
178
217
  if (!_fakerCache) _fakerCache = await import("@faker-js/faker");
@@ -280,6 +319,28 @@ function makeAsserter(value, negated = false) {
280
319
  asserter.gte = asserter.least;
281
320
  asserter.lt = asserter.below;
282
321
  asserter.lte = asserter.most;
322
+ asserter.oneOf = (list) => {
323
+ doAssert(
324
+ list.includes(value),
325
+ `Expected ${JSON.stringify(value)} to ${negated ? "not " : ""}be one of ${JSON.stringify(list)}`
326
+ );
327
+ return asserter;
328
+ };
329
+ asserter.lengthOf = (n) => {
330
+ const len = value.length;
331
+ doAssert(
332
+ len === n,
333
+ `Expected length ${len} to ${negated ? "not " : ""}equal ${n}`
334
+ );
335
+ return asserter;
336
+ };
337
+ asserter.match = (re) => {
338
+ doAssert(
339
+ re.test(String(value)),
340
+ `Expected "${value}" to ${negated ? "not " : ""}match ${re}`
341
+ );
342
+ return asserter;
343
+ };
283
344
  return asserter;
284
345
  }
285
346
  class AssertionError extends Error {
@@ -330,7 +391,9 @@ function buildAt(ctx, testResults, consoleOutput) {
330
391
  }
331
392
  },
332
393
  // Expect / assertions
333
- expect: (value) => makeAsserter(value, false)
394
+ expect: (value) => makeAsserter(value, false),
395
+ // JSONPath query: sp.jsonPath(data, '$.store.book[?(@.price < 10)].title')
396
+ jsonPath: (data, expr) => jsonpathPlus.JSONPath({ path: expr, json: data })
334
397
  };
335
398
  if (ctx.response) {
336
399
  const resp = ctx.response;
@@ -350,6 +413,11 @@ function buildAt(ctx, testResults, consoleOutput) {
350
413
  return parsedJson;
351
414
  },
352
415
  text: () => resp.body,
416
+ xmlText: (selector) => {
417
+ const doc = new xmldom.DOMParser().parseFromString(resp.body, "text/xml");
418
+ const node = xmlQuerySelector(doc, selector);
419
+ return node ? String(node.textContent ?? "").trim() : null;
420
+ },
353
421
  to: {
354
422
  have: {
355
423
  status: (code) => {
@@ -385,6 +453,7 @@ async function runScript(code, ctx, timeoutMs = 5e3) {
385
453
  };
386
454
  const sandbox = {
387
455
  sp,
456
+ DOMParser: makeSandboxDOMParser,
388
457
  dayjs,
389
458
  faker,
390
459
  tv4,
package/out/main/index.js CHANGED
@@ -25,7 +25,7 @@ const electron = require("electron");
25
25
  const path = require("path");
26
26
  const fs = require("fs");
27
27
  const promises = require("fs/promises");
28
- const scriptRunner = require("./chunks/script-runner-DIevRMmJ.js");
28
+ const scriptRunner = require("./chunks/script-runner-CmdLWmKN.js");
29
29
  const undici = require("undici");
30
30
  const crypto = require("crypto");
31
31
  const uuid = require("uuid");
@@ -39,6 +39,8 @@ const Ajv = require("ajv");
39
39
  require("vm");
40
40
  require("dayjs");
41
41
  require("tv4");
42
+ require("jsonpath-plus");
43
+ require("@xmldom/xmldom");
42
44
  const LAST_WS_FILE = path.join(electron.app.getPath("userData"), "last-workspace.json");
43
45
  async function saveLastWorkspacePath(wsPath) {
44
46
  await promises.writeFile(LAST_WS_FILE, JSON.stringify({ path: wsPath }), "utf8");
@@ -3970,6 +3972,11 @@ function createWindow() {
3970
3972
  win.show();
3971
3973
  }, 1200);
3972
3974
  });
3975
+ if (process.platform === "win32") {
3976
+ const version = electron.app.getVersion();
3977
+ win.setTitle(`api Spector${version ? ` v${version}` : ""}`);
3978
+ win.webContents.on("page-title-updated", (e) => e.preventDefault());
3979
+ }
3973
3980
  win.webContents.on("before-input-event", (_e, input) => {
3974
3981
  if (input.type !== "keyDown") return;
3975
3982
  const devToolsShortcut = input.key === "F12" || input.key === "I" && input.shift && (input.meta || input.control);
@@ -3,11 +3,13 @@
3
3
  const promises = require("fs/promises");
4
4
  const path = require("path");
5
5
  const undici = require("undici");
6
- const scriptRunner = require("./chunks/script-runner-DIevRMmJ.js");
6
+ const scriptRunner = require("./chunks/script-runner-CmdLWmKN.js");
7
7
  require("crypto");
8
8
  require("vm");
9
9
  require("dayjs");
10
10
  require("tv4");
11
+ require("jsonpath-plus");
12
+ require("@xmldom/xmldom");
11
13
  function buildJsonReport(results, summary, meta = {}) {
12
14
  return JSON.stringify({
13
15
  timestamp: meta.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
@@ -78,6 +78,8 @@ const api = {
78
78
  generateDocs: (payload) => electron.ipcRenderer.invoke("docs:generate", payload),
79
79
  // ─── Contract testing ─────────────────────────────────────────────────────
80
80
  runContracts: (payload) => electron.ipcRenderer.invoke("contract:run", payload),
81
- inferContractSchema: (jsonBody) => electron.ipcRenderer.invoke("contract:inferSchema", jsonBody)
81
+ inferContractSchema: (jsonBody) => electron.ipcRenderer.invoke("contract:inferSchema", jsonBody),
82
+ // ─── Platform ─────────────────────────────────────────────────────────────
83
+ platform: process.platform
82
84
  };
83
85
  electron.contextBridge.exposeInMainWorld("electron", api);
@@ -0,0 +1,2 @@
1
+ /*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */
2
+ @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-leading:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1}}}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.top-full{top:100%}.left-1\/2{left:50%}.z-50{z-index:50}.z-\[60\]{z-index:60}.z-\[200\]{z-index:200}.z-\[300\]{z-index:300}.col-1{grid-column:1}.col-2{grid-column:2}.mx-auto{margin-inline:auto}.mt-px{margin-top:1px}.-mb-px{margin-bottom:-1px}.ml-auto{margin-left:auto}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.table{display:table}.h-full{height:100%}.h-screen{height:100vh}.max-h-\[45vh\]{max-height:45vh}.max-h-\[70vh\]{max-height:70vh}.max-h-\[80vh\]{max-height:80vh}.min-h-\[160px\]{min-height:160px}.w-\[420px\]{width:420px}.w-\[480px\]{width:480px}.w-\[500px\]{width:500px}.w-\[520px\]{width:520px}.w-\[560px\]{width:560px}.w-\[680px\]{width:680px}.w-\[780px\]{width:780px}.w-full{width:100%}.w-px{width:1px}.max-w-\[60px\]{max-width:60px}.max-w-\[64px\]{max-width:64px}.max-w-\[120px\]{max-width:120px}.max-w-\[140px\]{max-width:140px}.max-w-\[180px\]{max-width:180px}.max-w-\[200px\]{max-width:200px}.max-w-\[260px\]{max-width:260px}.max-w-\[420px\]{max-width:420px}.min-w-\[72px\]{min-width:72px}.min-w-\[100px\]{min-width:100px}.min-w-\[120px\]{min-width:120px}.min-w-\[160px\]{min-width:160px}.min-w-\[220px\]{min-width:220px}.min-w-\[260px\]{min-width:260px}.flex-1{flex:1}.flex-shrink-0,.shrink-0{flex-shrink:0}.border-collapse{border-collapse:collapse}.-translate-x-1\/2{--tw-translate-x:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.rotate-180{rotate:180deg}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.resize-none{resize:none}.resize-y{resize:vertical}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.self-end{align-self:flex-end}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-x-hidden{overflow-x:hidden}.overflow-y-auto{overflow-y:auto}.rounded-full{border-radius:3.40282e38px}.border{border-style:var(--tw-border-style);border-width:1px}.border-x{border-inline-style:var(--tw-border-style);border-inline-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-l-2{border-left-style:var(--tw-border-style);border-left-width:2px}.border-transparent{border-color:#0000}.bg-\[\#1e1b2e\]{background-color:#1e1b2e}.bg-transparent{background-color:#0000}.py-px{padding-block:1px}.pt-\[20vh\]{padding-top:20vh}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.align-top{vertical-align:top}.text-\[9px\]{font-size:9px}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.leading-none{--tw-leading:1;line-height:1}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-\[var\(--text-primary\)\]{color:var(--text-primary)}.capitalize{text-transform:capitalize}.normal-case{text-transform:none}.uppercase{text-transform:uppercase}.italic{font-style:italic}.opacity-0{opacity:0}.opacity-30{opacity:.3}.opacity-50{opacity:.5}.ring-1{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.select-all{-webkit-user-select:all;user-select:all}.select-none{-webkit-user-select:none;user-select:none}.\[writing-mode\:vertical-rl\]{writing-mode:vertical-rl}@media (hover:hover){.group-hover\:flex:is(:where(.group):hover *){display:flex}.group-hover\:opacity-100:is(:where(.group):hover *){opacity:1}.group-hover\/tip\:block:is(:where(.group\/tip):hover *){display:block}}.last\:border-0:last-child{border-style:var(--tw-border-style);border-width:0}@media (hover:hover){.hover\:scale-150:hover{--tw-scale-x:150%;--tw-scale-y:150%;--tw-scale-z:150%;scale:var(--tw-scale-x) var(--tw-scale-y)}.hover\:text-\[var\(--text-primary\)\]:hover{color:var(--text-primary)}}.focus\:ring-1:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.disabled\:opacity-30:disabled{opacity:.3}.disabled\:opacity-40:disabled{opacity:.4}.disabled\:opacity-50:disabled{opacity:.5}:root{--ts-blue-400:#6aa3c8;--ts-blue-500:#3d7fb2;--ts-blue-600:#205d96;--ts-blue-700:#1a4e7e;--ts-green-400:#9fc93c;--ts-green-800:#4a5e1d;--ts-green-900:#2d3a12;--drag-region:28px;--surface-50:#f0eff4;--surface-100:#dddce3;--surface-200:#c4c2cb;--surface-400:#9d9aa8;--surface-500:#7a7785;--surface-700:#615e6d;--surface-800:#3d3b48;--surface-900:#312f3b;--surface-950:#272630;--text-primary:#e4e3ea;--text-muted:#9d9aa8;--bg-app:#272630;--border:#3d3b48}:root.light{--surface-50:#272630;--surface-100:#312f3b;--surface-200:#3d3b48;--surface-400:#615e6d;--surface-500:#716e7b;--surface-700:#908e9a;--surface-800:#c4c2cb;--surface-900:#d5d3dc;--surface-950:#e2e0e9;--text-primary:#1c1b24;--text-muted:#615e6d;--bg-app:#e2e0e9;--border:#c4c2cb;--ts-green-400:#567818}*{box-sizing:border-box}body{background:var(--bg-app);color:var(--text-primary);margin:0;padding:0;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;font-size:13px;overflow:hidden}.drag-region{-webkit-app-region:drag;height:var(--drag-region)}.no-drag{-webkit-app-region:no-drag}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:0 0}::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}.cm-editor{height:100%}.cm-scroller{overflow:auto}:root.light .text-white,:root.light .hover\:text-white:hover{color:var(--text-primary)!important}:root.light .bg-blue-500,:root.light .bg-blue-600,:root.light .bg-blue-700,:root.light .hover\:bg-blue-500:hover,:root.light .hover\:bg-blue-600:hover,:root.light .bg-emerald-600,:root.light .bg-red-600,:root.light .bg-amber-600,:root.light .bg-orange-600{color:#fff!important}:root.light input::placeholder,:root.light textarea::placeholder{opacity:1;color:var(--text-muted)!important}:root.light .bg-surface-800{background-color:var(--surface-800)}:root.light option{color:#1c1b24;background:#d5d3dc}:root.light .text-red-300{color:#b91c1c!important}:root.light .text-red-400{color:#dc2626!important}:root.light .text-red-500{color:#991b1b!important}:root.light .bg-red-900\/30{background-color:#fee2e2!important}:root.light .bg-red-900\/20{background-color:#fef2f2!important}:root.light .border-red-700,:root.light .border-red-800{border-color:#fca5a5!important}:root.light .text-emerald-300{color:#15803d!important}:root.light .text-emerald-400{color:#16a34a!important}:root.light .bg-emerald-900\/20{background-color:#f0fdf4!important}:root.light .bg-emerald-900\/30{background-color:#dcfce7!important}:root.light .border-emerald-800,:root.light .border-emerald-700{border-color:#86efac!important}:root.light .text-amber-300{color:#b45309!important}:root.light .text-amber-400{color:#d97706!important}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}