@testsmith/api-spector 0.4.1 → 0.4.3
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.
package/out/main/index.js
CHANGED
|
@@ -5174,7 +5174,7 @@ function isNewer(a, b) {
|
|
|
5174
5174
|
return a2 > b2;
|
|
5175
5175
|
}
|
|
5176
5176
|
async function checkForUpdate() {
|
|
5177
|
-
const current = "0.4.
|
|
5177
|
+
const current = "0.4.3";
|
|
5178
5178
|
try {
|
|
5179
5179
|
const res = await fetch(REGISTRY_URL, { signal: AbortSignal.timeout(4e3) });
|
|
5180
5180
|
if (!res.ok) return null;
|
package/out/main/runner.js
CHANGED
|
@@ -396,7 +396,7 @@ async function main() {
|
|
|
396
396
|
} else if (!envName && workspace.settings?.defaultEnvironment && !env) {
|
|
397
397
|
console.warn(cliCommon.color(`Warning: default environment "${workspace.settings.defaultEnvironment}" not found. Running without environment.`, cliCommon.C.yellow));
|
|
398
398
|
}
|
|
399
|
-
const version = `v${"0.4.
|
|
399
|
+
const version = `v${"0.4.3"}`;
|
|
400
400
|
console.log("");
|
|
401
401
|
console.log(cliCommon.color(" API Test Runner" + (version ? ` ${version}` : ""), cliCommon.C.bold, cliCommon.C.white));
|
|
402
402
|
console.log(cliCommon.color(` Workspace: ${wsPath}`, cliCommon.C.gray));
|
|
@@ -13535,6 +13535,7 @@ const createTabsSlice = (set2, get2) => ({
|
|
|
13535
13535
|
activeTabId: null,
|
|
13536
13536
|
activeCollectionId: null,
|
|
13537
13537
|
openInTab: (requestId, collectionId) => set2((s) => {
|
|
13538
|
+
s.collectionPanelOpen = false;
|
|
13538
13539
|
const existing = s.tabs.find((t2) => t2.requestId === requestId);
|
|
13539
13540
|
if (existing) {
|
|
13540
13541
|
s.activeTabId = existing.id;
|
|
@@ -13568,6 +13569,7 @@ const createTabsSlice = (set2, get2) => ({
|
|
|
13568
13569
|
}),
|
|
13569
13570
|
setActiveTabId: (id2) => set2((s) => {
|
|
13570
13571
|
s.activeTabId = id2;
|
|
13572
|
+
s.collectionPanelOpen = false;
|
|
13571
13573
|
const tab = s.tabs.find((t2) => t2.id === id2);
|
|
13572
13574
|
if (tab) s.activeCollectionId = tab.collectionId;
|
|
13573
13575
|
}),
|
|
@@ -14144,6 +14146,7 @@ const createUiSlice = (set2) => ({
|
|
|
14144
14146
|
activeGitDiff: null,
|
|
14145
14147
|
quickInsertsOpen: true,
|
|
14146
14148
|
sendSignal: 0,
|
|
14149
|
+
collectionPanelOpen: false,
|
|
14147
14150
|
setShowGeneratorPanel: (v) => set2((s) => {
|
|
14148
14151
|
s.showGeneratorPanel = v;
|
|
14149
14152
|
}),
|
|
@@ -14194,6 +14197,9 @@ const createUiSlice = (set2) => ({
|
|
|
14194
14197
|
}),
|
|
14195
14198
|
requestSend: () => set2((s) => {
|
|
14196
14199
|
s.sendSignal += 1;
|
|
14200
|
+
}),
|
|
14201
|
+
setCollectionPanelOpen: (open) => set2((s) => {
|
|
14202
|
+
s.collectionPanelOpen = open;
|
|
14197
14203
|
})
|
|
14198
14204
|
});
|
|
14199
14205
|
const useStore = create()(
|
|
@@ -35826,6 +35832,144 @@ function BearerPanel({
|
|
|
35826
35832
|
] })
|
|
35827
35833
|
] });
|
|
35828
35834
|
}
|
|
35835
|
+
function parseCSV(text) {
|
|
35836
|
+
const lines = text.trim().split(/\r?\n/).filter(Boolean);
|
|
35837
|
+
if (lines.length === 0) return { columns: [], rows: [] };
|
|
35838
|
+
function splitRow(line) {
|
|
35839
|
+
const cells = [];
|
|
35840
|
+
let cur2 = "";
|
|
35841
|
+
let inQuote = false;
|
|
35842
|
+
for (let i = 0; i < line.length; i++) {
|
|
35843
|
+
const ch = line[i];
|
|
35844
|
+
if (ch === '"') {
|
|
35845
|
+
inQuote = !inQuote;
|
|
35846
|
+
} else if (ch === "," && !inQuote) {
|
|
35847
|
+
cells.push(cur2.trim());
|
|
35848
|
+
cur2 = "";
|
|
35849
|
+
} else {
|
|
35850
|
+
cur2 += ch;
|
|
35851
|
+
}
|
|
35852
|
+
}
|
|
35853
|
+
cells.push(cur2.trim());
|
|
35854
|
+
return cells;
|
|
35855
|
+
}
|
|
35856
|
+
const columns = splitRow(lines[0]);
|
|
35857
|
+
const rows = lines.slice(1).map(splitRow);
|
|
35858
|
+
return { columns, rows };
|
|
35859
|
+
}
|
|
35860
|
+
function toCSV(ds) {
|
|
35861
|
+
const escape2 = (s) => s.includes(",") || s.includes('"') ? `"${s.replace(/"/g, '""')}"` : s;
|
|
35862
|
+
return [ds.columns, ...ds.rows].map((row) => row.map(escape2).join(",")).join("\n");
|
|
35863
|
+
}
|
|
35864
|
+
function DataSetEditor({ ds, onChange, exportName, scopeLabel = "collection" }) {
|
|
35865
|
+
const csvFileRef = reactExports.useRef(null);
|
|
35866
|
+
function addColumn() {
|
|
35867
|
+
const name2 = `var${ds.columns.length + 1}`;
|
|
35868
|
+
onChange({ columns: [...ds.columns, name2], rows: ds.rows.map((r) => [...r, ""]) });
|
|
35869
|
+
}
|
|
35870
|
+
function renameColumn(ci, name2) {
|
|
35871
|
+
onChange({ ...ds, columns: ds.columns.map((c, i) => i === ci ? name2 : c) });
|
|
35872
|
+
}
|
|
35873
|
+
function removeColumn(ci) {
|
|
35874
|
+
onChange({ columns: ds.columns.filter((_, i) => i !== ci), rows: ds.rows.map((r) => r.filter((_, i) => i !== ci)) });
|
|
35875
|
+
}
|
|
35876
|
+
function addRow() {
|
|
35877
|
+
onChange({ ...ds, rows: [...ds.rows, ds.columns.map(() => "")] });
|
|
35878
|
+
}
|
|
35879
|
+
function setCell(ri, ci, v) {
|
|
35880
|
+
onChange({ ...ds, rows: ds.rows.map((row, i) => i === ri ? row.map((c, j) => j === ci ? v : c) : row) });
|
|
35881
|
+
}
|
|
35882
|
+
function removeRow(ri) {
|
|
35883
|
+
onChange({ ...ds, rows: ds.rows.filter((_, i) => i !== ri) });
|
|
35884
|
+
}
|
|
35885
|
+
function importCSV(e) {
|
|
35886
|
+
const file = e.target.files?.[0];
|
|
35887
|
+
if (!file) return;
|
|
35888
|
+
const reader = new FileReader();
|
|
35889
|
+
reader.onload = (ev) => onChange(parseCSV(ev.target?.result));
|
|
35890
|
+
reader.readAsText(file);
|
|
35891
|
+
e.target.value = "";
|
|
35892
|
+
}
|
|
35893
|
+
function exportCSV() {
|
|
35894
|
+
const blob = new Blob([toCSV(ds)], { type: "text/csv" });
|
|
35895
|
+
const url = URL.createObjectURL(blob);
|
|
35896
|
+
const a = document.createElement("a");
|
|
35897
|
+
a.href = url;
|
|
35898
|
+
a.download = `${exportName.replace(/\s+/g, "_")}_data.csv`;
|
|
35899
|
+
a.click();
|
|
35900
|
+
URL.revokeObjectURL(url);
|
|
35901
|
+
}
|
|
35902
|
+
const hasColumns = ds.columns.length > 0;
|
|
35903
|
+
const iterCount = ds.rows.length;
|
|
35904
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-3 text-xs", children: [
|
|
35905
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-surface-600 text-[11px]", children: [
|
|
35906
|
+
"Define variables here - each row runs the entire ",
|
|
35907
|
+
scopeLabel,
|
|
35908
|
+
" once with those values injected. Columns become ",
|
|
35909
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "text-surface-500", children: "{{variable}}" }),
|
|
35910
|
+
" placeholders."
|
|
35911
|
+
] }),
|
|
35912
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [
|
|
35913
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: addColumn, className: "px-2.5 py-1 bg-surface-700 hover:bg-surface-600 rounded transition-colors", children: "+ Column" }),
|
|
35914
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: addRow, disabled: !hasColumns, className: "px-2.5 py-1 bg-surface-700 hover:bg-surface-600 disabled:opacity-40 rounded transition-colors", children: "+ Row" }),
|
|
35915
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1" }),
|
|
35916
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
35917
|
+
"button",
|
|
35918
|
+
{
|
|
35919
|
+
onClick: () => csvFileRef.current?.click(),
|
|
35920
|
+
className: "px-2.5 py-1 bg-surface-700 hover:bg-surface-600 rounded transition-colors",
|
|
35921
|
+
title: "Import CSV - first row is column headers",
|
|
35922
|
+
children: "↑ Import CSV"
|
|
35923
|
+
}
|
|
35924
|
+
),
|
|
35925
|
+
hasColumns && iterCount > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: exportCSV, className: "px-2.5 py-1 bg-surface-700 hover:bg-surface-600 rounded transition-colors", children: "↓ Export CSV" }),
|
|
35926
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("input", { ref: csvFileRef, type: "file", accept: ".csv,text/csv", className: "hidden", onChange: importCSV })
|
|
35927
|
+
] }),
|
|
35928
|
+
hasColumns && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-surface-500", children: iterCount === 0 ? "No rows yet - add rows or import a CSV." : `${iterCount} iteration${iterCount !== 1 ? "s" : ""} · columns: ${ds.columns.join(", ")}` }),
|
|
35929
|
+
hasColumns ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("table", { className: "w-full border-collapse text-xs", children: [
|
|
35930
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("thead", { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { className: "border-b border-surface-700", children: [
|
|
35931
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "w-8 px-2 py-1 text-surface-600 font-normal text-left", children: "#" }),
|
|
35932
|
+
ds.columns.map((col, ci) => /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "px-1 py-1 font-normal text-left min-w-[120px]", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [
|
|
35933
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
35934
|
+
"input",
|
|
35935
|
+
{
|
|
35936
|
+
value: col,
|
|
35937
|
+
onChange: (e) => renameColumn(ci, e.target.value),
|
|
35938
|
+
className: "flex-1 bg-surface-800 border border-surface-700 rounded px-1.5 py-0.5 font-mono text-blue-400 focus:outline-none focus:border-blue-500",
|
|
35939
|
+
title: "Variable name"
|
|
35940
|
+
}
|
|
35941
|
+
),
|
|
35942
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => removeColumn(ci), className: "text-surface-400 hover:text-red-400 transition-colors shrink-0", children: "×" })
|
|
35943
|
+
] }) }, ci)),
|
|
35944
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "w-6" })
|
|
35945
|
+
] }) }),
|
|
35946
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("tbody", { children: [
|
|
35947
|
+
ds.rows.map((row, ri) => /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { className: "group border-b border-surface-800/60 hover:bg-surface-800/30", children: [
|
|
35948
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-2 py-1 text-surface-600", children: ri + 1 }),
|
|
35949
|
+
ds.columns.map((_, ci) => /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-1 py-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
35950
|
+
"input",
|
|
35951
|
+
{
|
|
35952
|
+
value: row[ci] ?? "",
|
|
35953
|
+
onChange: (e) => setCell(ri, ci, e.target.value),
|
|
35954
|
+
className: "w-full bg-surface-800 border border-transparent rounded px-1.5 py-0.5 font-mono focus:outline-none focus:border-blue-500 hover:border-surface-600"
|
|
35955
|
+
}
|
|
35956
|
+
) }, ci)),
|
|
35957
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-1 py-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => removeRow(ri), className: "text-surface-400 hover:text-red-400 opacity-0 group-hover:opacity-100 transition-all", children: "×" }) })
|
|
35958
|
+
] }, ri)),
|
|
35959
|
+
iterCount === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("tr", { children: /* @__PURE__ */ jsxRuntimeExports.jsx("td", { colSpan: ds.columns.length + 2, className: "px-2 py-3 text-surface-600 text-center", children: 'No rows - click "+ Row" or import a CSV' }) })
|
|
35960
|
+
] })
|
|
35961
|
+
] }) }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center gap-2 py-8 text-surface-600", children: [
|
|
35962
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "No columns defined." }),
|
|
35963
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[10px]", children: [
|
|
35964
|
+
"Click ",
|
|
35965
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "+ Column" }),
|
|
35966
|
+
" to add a variable, or ",
|
|
35967
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "↑ Import CSV" }),
|
|
35968
|
+
" to load from a file."
|
|
35969
|
+
] })
|
|
35970
|
+
] })
|
|
35971
|
+
] });
|
|
35972
|
+
}
|
|
35829
35973
|
function FolderSettingsModal({ collectionId, folder, onClose }) {
|
|
35830
35974
|
const updateFolder = useStore((s) => s.updateFolder);
|
|
35831
35975
|
const [activeTab, setActiveTab] = reactExports.useState("auth");
|
|
@@ -35834,6 +35978,7 @@ function FolderSettingsModal({ collectionId, folder, onClose }) {
|
|
|
35834
35978
|
const [varRows, setVarRows] = reactExports.useState(
|
|
35835
35979
|
Object.entries(folder.variables ?? {}).map(([key, value]) => ({ key, value, enabled: true }))
|
|
35836
35980
|
);
|
|
35981
|
+
const [dataSet, setDataSet] = reactExports.useState(folder.dataSet ?? { columns: [], rows: [] });
|
|
35837
35982
|
function patchAuth(patch) {
|
|
35838
35983
|
setAuth((prev) => ({ ...prev, ...patch }));
|
|
35839
35984
|
}
|
|
@@ -35841,7 +35986,8 @@ function FolderSettingsModal({ collectionId, folder, onClose }) {
|
|
|
35841
35986
|
const variables = Object.fromEntries(
|
|
35842
35987
|
varRows.filter((r) => r.key.trim()).map((r) => [r.key.trim(), r.value])
|
|
35843
35988
|
);
|
|
35844
|
-
|
|
35989
|
+
const cleanData = dataSet.columns.length > 0 ? dataSet : void 0;
|
|
35990
|
+
updateFolder(collectionId, folder.id, { auth, headers, variables, dataSet: cleanData });
|
|
35845
35991
|
onClose();
|
|
35846
35992
|
}
|
|
35847
35993
|
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
@@ -35861,7 +36007,7 @@ function FolderSettingsModal({ collectionId, folder, onClose }) {
|
|
|
35861
36007
|
] }),
|
|
35862
36008
|
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "text-surface-400 hover:text-white text-lg leading-none", children: "×" })
|
|
35863
36009
|
] }),
|
|
35864
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex border-b border-surface-800 px-4 shrink-0", children: ["auth", "headers", "variables"].map((t2) => /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
36010
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex border-b border-surface-800 px-4 shrink-0", children: ["auth", "headers", "variables", "data"].map((t2) => /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
35865
36011
|
"button",
|
|
35866
36012
|
{
|
|
35867
36013
|
onClick: () => setActiveTab(t2),
|
|
@@ -35904,7 +36050,8 @@ function FolderSettingsModal({ collectionId, folder, onClose }) {
|
|
|
35904
36050
|
valuePlaceholder: "value"
|
|
35905
36051
|
}
|
|
35906
36052
|
)
|
|
35907
|
-
] })
|
|
36053
|
+
] }),
|
|
36054
|
+
activeTab === "data" && /* @__PURE__ */ jsxRuntimeExports.jsx(DataSetEditor, { ds: dataSet, onChange: setDataSet, exportName: folder.name, scopeLabel: "folder" })
|
|
35908
36055
|
] }),
|
|
35909
36056
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2 px-4 py-3 border-t border-surface-800 shrink-0", children: [
|
|
35910
36057
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
@@ -36891,6 +37038,7 @@ function CollectionTree() {
|
|
|
36891
37038
|
const tabs = useStore((s) => s.tabs);
|
|
36892
37039
|
const openInTab = useStore((s) => s.openInTab);
|
|
36893
37040
|
const setActiveCollection = useStore((s) => s.setActiveCollection);
|
|
37041
|
+
const setCollectionPanelOpen = useStore((s) => s.setCollectionPanelOpen);
|
|
36894
37042
|
const activeRequestId = tabs.find((t2) => t2.id === activeTabId)?.requestId ?? null;
|
|
36895
37043
|
const addCollection = useStore((s) => s.addCollection);
|
|
36896
37044
|
const addRequest = useStore((s) => s.addRequest);
|
|
@@ -36948,7 +37096,10 @@ function CollectionTree() {
|
|
|
36948
37096
|
isActive: col.id === activeCollectionId,
|
|
36949
37097
|
activeRequestId,
|
|
36950
37098
|
existingCollectionNames: colList.map((c) => c.data.name),
|
|
36951
|
-
onSelectCollection: () =>
|
|
37099
|
+
onSelectCollection: () => {
|
|
37100
|
+
setActiveCollection(col.id);
|
|
37101
|
+
setCollectionPanelOpen(true);
|
|
37102
|
+
},
|
|
36952
37103
|
onSelectRequest: (reqId) => openInTab(reqId, col.id),
|
|
36953
37104
|
newRequestId,
|
|
36954
37105
|
onAddRequest: (folderId) => setNewRequestId(addRequest(col.id, folderId)),
|
|
@@ -66235,7 +66386,7 @@ function WelcomeScreen() {
|
|
|
66235
66386
|
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-surface-400 text-sm max-w-sm", children: "Local-first API testing with Robot Framework & Playwright code generation. Secrets stay on your machine." }),
|
|
66236
66387
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[11px] mt-3", style: { color: "var(--text-muted)" }, children: [
|
|
66237
66388
|
"version ",
|
|
66238
|
-
"0.4.
|
|
66389
|
+
"0.4.3"
|
|
66239
66390
|
] })
|
|
66240
66391
|
] }),
|
|
66241
66392
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-3 w-64", children: [
|
|
@@ -68992,7 +69143,7 @@ function RunnerModal() {
|
|
|
68992
69143
|
const colEntry = collectionId ? collections[collectionId] : null;
|
|
68993
69144
|
const colName = colEntry?.data.name ?? "Collection";
|
|
68994
69145
|
const folderName = folderId && colEntry ? findFolder$1(colEntry.data.rootFolder, folderId)?.name ?? "Folder" : null;
|
|
68995
|
-
const dataSet = colEntry?.data.dataSet ?? { columns: [], rows: [] };
|
|
69146
|
+
const dataSet = (folderId && colEntry ? findFolder$1(colEntry.data.rootFolder, folderId)?.dataSet : null) ?? colEntry?.data.dataSet ?? { columns: [], rows: [] };
|
|
68996
69147
|
const iterCount = dataSet.rows.length;
|
|
68997
69148
|
const availableTags = collectionId ? allTagsIn(collectionId, folderId) : [];
|
|
68998
69149
|
const progressIdxRef = reactExports.useRef(0);
|
|
@@ -69008,7 +69159,7 @@ function RunnerModal() {
|
|
|
69008
69159
|
}, [runnerModal.open]);
|
|
69009
69160
|
const toggleTag = (tag) => setFilterTags((prev) => prev.includes(tag) ? prev.filter((t2) => t2 !== tag) : [...prev, tag]);
|
|
69010
69161
|
const run = reactExports.useCallback(async () => {
|
|
69011
|
-
const ds = colEntry?.data.dataSet ?? { columns: [], rows: [] };
|
|
69162
|
+
const ds = (folderId && colEntry ? findFolder$1(colEntry.data.rootFolder, folderId)?.dataSet : null) ?? colEntry?.data.dataSet ?? { columns: [], rows: [] };
|
|
69012
69163
|
const baseItems = collectionId ? collectRequests(collectionId, folderId, filterTags) : [];
|
|
69013
69164
|
if (baseItems.length === 0) return;
|
|
69014
69165
|
let items2;
|
|
@@ -69313,42 +69464,12 @@ function RunnerModal() {
|
|
|
69313
69464
|
}
|
|
69314
69465
|
);
|
|
69315
69466
|
}
|
|
69316
|
-
function parseCSV(text) {
|
|
69317
|
-
const lines = text.trim().split(/\r?\n/).filter(Boolean);
|
|
69318
|
-
if (lines.length === 0) return { columns: [], rows: [] };
|
|
69319
|
-
function splitRow(line) {
|
|
69320
|
-
const cells = [];
|
|
69321
|
-
let cur2 = "";
|
|
69322
|
-
let inQuote = false;
|
|
69323
|
-
for (let i = 0; i < line.length; i++) {
|
|
69324
|
-
const ch = line[i];
|
|
69325
|
-
if (ch === '"') {
|
|
69326
|
-
inQuote = !inQuote;
|
|
69327
|
-
} else if (ch === "," && !inQuote) {
|
|
69328
|
-
cells.push(cur2.trim());
|
|
69329
|
-
cur2 = "";
|
|
69330
|
-
} else {
|
|
69331
|
-
cur2 += ch;
|
|
69332
|
-
}
|
|
69333
|
-
}
|
|
69334
|
-
cells.push(cur2.trim());
|
|
69335
|
-
return cells;
|
|
69336
|
-
}
|
|
69337
|
-
const columns = splitRow(lines[0]);
|
|
69338
|
-
const rows = lines.slice(1).map(splitRow);
|
|
69339
|
-
return { columns, rows };
|
|
69340
|
-
}
|
|
69341
|
-
function toCSV(ds) {
|
|
69342
|
-
const escape2 = (s) => s.includes(",") || s.includes('"') ? `"${s.replace(/"/g, '""')}"` : s;
|
|
69343
|
-
return [ds.columns, ...ds.rows].map((row) => row.map(escape2).join(",")).join("\n");
|
|
69344
|
-
}
|
|
69345
69467
|
function CollectionPanel() {
|
|
69346
69468
|
const activeCollectionId = useStore((s) => s.activeCollectionId);
|
|
69347
69469
|
const collections = useStore((s) => s.collections);
|
|
69348
69470
|
const updateCollectionDataSet = useStore((s) => s.updateCollectionDataSet);
|
|
69349
69471
|
const openRunner = useStore((s) => s.openRunner);
|
|
69350
69472
|
const [activeTab, setActiveTab] = reactExports.useState("data");
|
|
69351
|
-
const csvFileRef = reactExports.useRef(null);
|
|
69352
69473
|
if (!activeCollectionId) {
|
|
69353
69474
|
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-center h-full text-surface-400 text-sm", children: "Select a request from the sidebar" });
|
|
69354
69475
|
}
|
|
@@ -69358,43 +69479,6 @@ function CollectionPanel() {
|
|
|
69358
69479
|
function setDs(next) {
|
|
69359
69480
|
updateCollectionDataSet(activeCollectionId, next);
|
|
69360
69481
|
}
|
|
69361
|
-
function addColumn() {
|
|
69362
|
-
const name2 = `var${ds.columns.length + 1}`;
|
|
69363
|
-
setDs({ columns: [...ds.columns, name2], rows: ds.rows.map((r) => [...r, ""]) });
|
|
69364
|
-
}
|
|
69365
|
-
function renameColumn(ci, name2) {
|
|
69366
|
-
setDs({ ...ds, columns: ds.columns.map((c, i) => i === ci ? name2 : c) });
|
|
69367
|
-
}
|
|
69368
|
-
function removeColumn(ci) {
|
|
69369
|
-
setDs({ columns: ds.columns.filter((_, i) => i !== ci), rows: ds.rows.map((r) => r.filter((_, i) => i !== ci)) });
|
|
69370
|
-
}
|
|
69371
|
-
function addRow() {
|
|
69372
|
-
setDs({ ...ds, rows: [...ds.rows, ds.columns.map(() => "")] });
|
|
69373
|
-
}
|
|
69374
|
-
function setCell(ri, ci, v) {
|
|
69375
|
-
setDs({ ...ds, rows: ds.rows.map((row, i) => i === ri ? row.map((c, j) => j === ci ? v : c) : row) });
|
|
69376
|
-
}
|
|
69377
|
-
function removeRow(ri) {
|
|
69378
|
-
setDs({ ...ds, rows: ds.rows.filter((_, i) => i !== ri) });
|
|
69379
|
-
}
|
|
69380
|
-
function importCSV(e) {
|
|
69381
|
-
const file = e.target.files?.[0];
|
|
69382
|
-
if (!file) return;
|
|
69383
|
-
const reader = new FileReader();
|
|
69384
|
-
reader.onload = (ev) => setDs(parseCSV(ev.target?.result));
|
|
69385
|
-
reader.readAsText(file);
|
|
69386
|
-
e.target.value = "";
|
|
69387
|
-
}
|
|
69388
|
-
function exportCSV() {
|
|
69389
|
-
const blob = new Blob([toCSV(ds)], { type: "text/csv" });
|
|
69390
|
-
const url = URL.createObjectURL(blob);
|
|
69391
|
-
const a = document.createElement("a");
|
|
69392
|
-
a.href = url;
|
|
69393
|
-
a.download = `${col.name.replace(/\s+/g, "_")}_data.csv`;
|
|
69394
|
-
a.click();
|
|
69395
|
-
URL.revokeObjectURL(url);
|
|
69396
|
-
}
|
|
69397
|
-
const hasColumns = ds.columns.length > 0;
|
|
69398
69482
|
const iterCount = ds.rows.length;
|
|
69399
69483
|
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col h-full", children: [
|
|
69400
69484
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-6 py-4 border-b border-surface-800 flex-shrink-0 flex items-center justify-between", children: [
|
|
@@ -69434,72 +69518,7 @@ function CollectionPanel() {
|
|
|
69434
69518
|
tab.id
|
|
69435
69519
|
)) }),
|
|
69436
69520
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 overflow-y-auto px-6 py-4", children: [
|
|
69437
|
-
activeTab === "data" && /* @__PURE__ */ jsxRuntimeExports.
|
|
69438
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-surface-600 text-[11px]", children: [
|
|
69439
|
-
"Define variables here - each row runs the entire collection once with those values injected. Columns become ",
|
|
69440
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "text-surface-500", children: "{{variable}}" }),
|
|
69441
|
-
" placeholders."
|
|
69442
|
-
] }),
|
|
69443
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [
|
|
69444
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: addColumn, className: "px-2.5 py-1 bg-surface-700 hover:bg-surface-600 rounded transition-colors", children: "+ Column" }),
|
|
69445
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: addRow, disabled: !hasColumns, className: "px-2.5 py-1 bg-surface-700 hover:bg-surface-600 disabled:opacity-40 rounded transition-colors", children: "+ Row" }),
|
|
69446
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1" }),
|
|
69447
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
69448
|
-
"button",
|
|
69449
|
-
{
|
|
69450
|
-
onClick: () => csvFileRef.current?.click(),
|
|
69451
|
-
className: "px-2.5 py-1 bg-surface-700 hover:bg-surface-600 rounded transition-colors",
|
|
69452
|
-
title: "Import CSV - first row is column headers",
|
|
69453
|
-
children: "↑ Import CSV"
|
|
69454
|
-
}
|
|
69455
|
-
),
|
|
69456
|
-
hasColumns && iterCount > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: exportCSV, className: "px-2.5 py-1 bg-surface-700 hover:bg-surface-600 rounded transition-colors", children: "↓ Export CSV" }),
|
|
69457
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("input", { ref: csvFileRef, type: "file", accept: ".csv,text/csv", className: "hidden", onChange: importCSV })
|
|
69458
|
-
] }),
|
|
69459
|
-
hasColumns && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-surface-500", children: iterCount === 0 ? "No rows yet - add rows or import a CSV." : `${iterCount} iteration${iterCount !== 1 ? "s" : ""} · columns: ${ds.columns.join(", ")}` }),
|
|
69460
|
-
hasColumns ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("table", { className: "w-full border-collapse text-xs", children: [
|
|
69461
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("thead", { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { className: "border-b border-surface-700", children: [
|
|
69462
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "w-8 px-2 py-1 text-surface-600 font-normal text-left", children: "#" }),
|
|
69463
|
-
ds.columns.map((col2, ci) => /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "px-1 py-1 font-normal text-left min-w-[120px]", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [
|
|
69464
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
69465
|
-
"input",
|
|
69466
|
-
{
|
|
69467
|
-
value: col2,
|
|
69468
|
-
onChange: (e) => renameColumn(ci, e.target.value),
|
|
69469
|
-
className: "flex-1 bg-surface-800 border border-surface-700 rounded px-1.5 py-0.5 font-mono text-blue-400 focus:outline-none focus:border-blue-500",
|
|
69470
|
-
title: "Variable name"
|
|
69471
|
-
}
|
|
69472
|
-
),
|
|
69473
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => removeColumn(ci), className: "text-surface-400 hover:text-red-400 transition-colors shrink-0", children: "×" })
|
|
69474
|
-
] }) }, ci)),
|
|
69475
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "w-6" })
|
|
69476
|
-
] }) }),
|
|
69477
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("tbody", { children: [
|
|
69478
|
-
ds.rows.map((row, ri) => /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { className: "group border-b border-surface-800/60 hover:bg-surface-800/30", children: [
|
|
69479
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-2 py-1 text-surface-600", children: ri + 1 }),
|
|
69480
|
-
ds.columns.map((_, ci) => /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-1 py-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
69481
|
-
"input",
|
|
69482
|
-
{
|
|
69483
|
-
value: row[ci] ?? "",
|
|
69484
|
-
onChange: (e) => setCell(ri, ci, e.target.value),
|
|
69485
|
-
className: "w-full bg-surface-800 border border-transparent rounded px-1.5 py-0.5 font-mono focus:outline-none focus:border-blue-500 hover:border-surface-600"
|
|
69486
|
-
}
|
|
69487
|
-
) }, ci)),
|
|
69488
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-1 py-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => removeRow(ri), className: "text-surface-400 hover:text-red-400 opacity-0 group-hover:opacity-100 transition-all", children: "×" }) })
|
|
69489
|
-
] }, ri)),
|
|
69490
|
-
iterCount === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("tr", { children: /* @__PURE__ */ jsxRuntimeExports.jsx("td", { colSpan: ds.columns.length + 2, className: "px-2 py-3 text-surface-600 text-center", children: 'No rows - click "+ Row" or import a CSV' }) })
|
|
69491
|
-
] })
|
|
69492
|
-
] }) }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center gap-2 py-8 text-surface-600", children: [
|
|
69493
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "No columns defined." }),
|
|
69494
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[10px]", children: [
|
|
69495
|
-
"Click ",
|
|
69496
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "+ Column" }),
|
|
69497
|
-
" to add a variable, or ",
|
|
69498
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "↑ Import CSV" }),
|
|
69499
|
-
" to load from a file."
|
|
69500
|
-
] })
|
|
69501
|
-
] })
|
|
69502
|
-
] }),
|
|
69521
|
+
activeTab === "data" && /* @__PURE__ */ jsxRuntimeExports.jsx(DataSetEditor, { ds, onChange: setDs, exportName: col.name, scopeLabel: "collection" }),
|
|
69503
69522
|
activeTab === "variables" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-col gap-2 text-xs text-surface-600", children: Object.keys(col.collectionVariables ?? {}).length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { children: [
|
|
69504
69523
|
"No collection variables. Scripts can set them via ",
|
|
69505
69524
|
/* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "text-surface-500", children: "sp.collectionVariables.set(…)" }),
|
|
@@ -72463,6 +72482,7 @@ function App() {
|
|
|
72463
72482
|
const [tabContextMenu, setTabContextMenu] = reactExports.useState(null);
|
|
72464
72483
|
const showGeneratorPanel = useStore((s) => s.showGeneratorPanel);
|
|
72465
72484
|
const sidebarTab = useStore((s) => s.sidebarTab);
|
|
72485
|
+
const collectionPanelOpen = useStore((s) => s.collectionPanelOpen);
|
|
72466
72486
|
const setSidebarTab = useStore((s) => s.setSidebarTab);
|
|
72467
72487
|
const historyCount = useStore((s) => s.history.length);
|
|
72468
72488
|
const addCollection = useStore((s) => s.addCollection);
|
|
@@ -72580,7 +72600,7 @@ function App() {
|
|
|
72580
72600
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "#6aa3c8" }, children: "Spector" }),
|
|
72581
72601
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "ml-2 text-[10px] font-normal opacity-50", children: [
|
|
72582
72602
|
"v",
|
|
72583
|
-
"0.4.
|
|
72603
|
+
"0.4.3"
|
|
72584
72604
|
] }),
|
|
72585
72605
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "mx-2 opacity-30", children: "·" }),
|
|
72586
72606
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
@@ -72811,7 +72831,7 @@ function App() {
|
|
|
72811
72831
|
}
|
|
72812
72832
|
}
|
|
72813
72833
|
}
|
|
72814
|
-
) }) : sidebarTab === "mocks" && activeMockId ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(MockDetailPanel, { mockId: activeMockId }) }) : activeRequest ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex min-h-0", children: [
|
|
72834
|
+
) }) : sidebarTab === "mocks" && activeMockId ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(MockDetailPanel, { mockId: activeMockId }) }) : collectionPanelOpen ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(CollectionPanel, {}) }) : activeRequest ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex min-h-0", children: [
|
|
72815
72835
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
72816
72836
|
"div",
|
|
72817
72837
|
{
|
package/out/renderer/index.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta charset="UTF-8" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>API Spector</title>
|
|
8
|
-
<script type="module" crossorigin src="./assets/index-
|
|
8
|
+
<script type="module" crossorigin src="./assets/index-nGZ7Xq6I.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="./assets/index-DkZ0bxof.css">
|
|
10
10
|
</head>
|
|
11
11
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@testsmith/api-spector",
|
|
3
3
|
"productName": "API Spector",
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.3",
|
|
5
5
|
"description": "Local-first API testing tool to inspect, test and mock APIs",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -112,7 +112,23 @@
|
|
|
112
112
|
}
|
|
113
113
|
],
|
|
114
114
|
"mac": {
|
|
115
|
-
"category": "public.app-category.developer-tools"
|
|
115
|
+
"category": "public.app-category.developer-tools",
|
|
116
|
+
"target": [
|
|
117
|
+
{
|
|
118
|
+
"target": "dmg",
|
|
119
|
+
"arch": [
|
|
120
|
+
"arm64",
|
|
121
|
+
"x64"
|
|
122
|
+
]
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"target": "zip",
|
|
126
|
+
"arch": [
|
|
127
|
+
"arm64",
|
|
128
|
+
"x64"
|
|
129
|
+
]
|
|
130
|
+
}
|
|
131
|
+
]
|
|
116
132
|
},
|
|
117
133
|
"win": {
|
|
118
134
|
"target": "nsis"
|
|
@@ -124,21 +140,5 @@
|
|
|
124
140
|
"provider": "github",
|
|
125
141
|
"releaseType": "draft"
|
|
126
142
|
}
|
|
127
|
-
},
|
|
128
|
-
"mac": {
|
|
129
|
-
"category": "public.app-category.developer-tools",
|
|
130
|
-
"target": [
|
|
131
|
-
{
|
|
132
|
-
"target": "dmg",
|
|
133
|
-
"arch": [
|
|
134
|
-
"arm64",
|
|
135
|
-
"x64"
|
|
136
|
-
]
|
|
137
|
-
}
|
|
138
|
-
],
|
|
139
|
-
"identity": null
|
|
140
|
-
},
|
|
141
|
-
"win": {
|
|
142
|
-
"target": "nsis"
|
|
143
143
|
}
|
|
144
144
|
}
|