Skip to content

Commit 36e064b

Browse files
authored
feat: import circuit json (#599)
* feat: import circuit json * patch: naming * chore: removed caps as per review * fix: json badge color * chore: format * patch: json crd type badge * feat: Allow them to paste or upload circuit json, not a url * test: broken tests because playwright not working on my device * tests: sad * wtf * add 4 tests * tests: circuit json import modal full tests * chore: deleted debug file * chore: remove unused dep * validCircuitJson -> exampleCircuitJson
1 parent d76f839 commit 36e064b

File tree

6 files changed

+859
-2
lines changed

6 files changed

+859
-2
lines changed

bun.lock

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
"circuit-json-to-gerber": "^0.0.16",
6262
"circuit-json-to-pnp-csv": "^0.0.6",
6363
"circuit-json-to-readable-netlist": "^0.0.7",
64+
"circuit-json-to-tscircuit": "^0.0.4",
6465
"class-variance-authority": "^0.7.0",
6566
"clsx": "^2.1.1",
6667
"cmdk": "^1.0.4",
@@ -115,6 +116,7 @@
115116
"@types/babel__standalone": "^7.1.7",
116117
"@types/bun": "^1.1.10",
117118
"@types/country-list": "^2.1.4",
119+
"@types/node": "^22.13.0",
118120
"@types/prismjs": "^1.26.4",
119121
"@types/react": "^18.3.9",
120122
"@types/react-dom": "^18.3.0",
@@ -805,7 +807,7 @@
805807

806808
"@types/ms": ["@types/[email protected]", "", {}, "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g=="],
807809

808-
"@types/node": ["@types/node@18.19.54", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-+BRgt0G5gYjTvdLac9sIeE0iZcJxi4Jc4PV5EUzqi+88jmQLr+fRZdv2tCTV7IHKSGxM6SaLoOXQWWUiLUItMw=="],
810+
"@types/node": ["@types/node@22.13.0", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-ClIbNe36lawluuvq3+YYhnIN2CELi+6q8NpnM7PYp4hBn/TatfboPgVSm2rwKRfnV2M+Ty9GWDFI64KEe+kysA=="],
809811

810812
"@types/node-fetch": ["@types/[email protected]", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.0" } }, "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g=="],
811813

@@ -1041,6 +1043,8 @@
10411043

10421044
"circuit-json-to-readable-netlist": ["[email protected]", "", { "dependencies": { "@tscircuit/core": "^0.0.286", "@tscircuit/soup-util": "^0.0.41", "circuit-json-to-connectivity-map": "^0.0.17" }, "peerDependencies": { "typescript": "^5.7.2" } }, "sha512-GvlVMzEzLpB9WTsLkN4p5aHITjKhfEOQKFjZaUNQrd3FbyFbUXnx1e8vR1cB2M0fQXwaUQ0cxGTjK3W2AihKng=="],
10431045

1046+
"circuit-json-to-tscircuit": ["[email protected]", "", { "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-LpHbOwdPE4+CooWPAPoKXWs4vxTrjJgu/avnxE3AqGwCD9r0ZnT73mEAB9oQi6T1i7T53zdkSR6y+zpsyCSE7g=="],
1047+
10441048
"circuit-to-svg": ["[email protected]", "", { "dependencies": { "@tscircuit/footprinter": "^0.0.91", "@tscircuit/routing": "^1.3.5", "@tscircuit/soup-util": "^0.0.41", "@types/node": "^22.5.5", "bun-types": "^1.1.40", "svgson": "^5.3.1", "transformation-matrix": "^2.16.1" }, "peerDependencies": { "circuit-json": "*", "schematic-symbols": "*" } }, "sha512-AwTD5Ww5ujzK5pEkrVDFtFx5nfGqVbtbIHgXNEeji5RKfDpb0WzeXtaw75kkHl715JB1WBwUupewKO7mTaI06A=="],
10451049

10461050
"class-variance-authority": ["[email protected]", "", { "dependencies": { "clsx": "2.0.0" } }, "sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A=="],
@@ -2301,7 +2305,7 @@
23012305

23022306
"uint8array-extras": ["[email protected]", "", {}, "sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ=="],
23032307

2304-
"undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
2308+
"undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
23052309

23062310
"unicorn-magic": ["[email protected]", "", {}, "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ=="],
23072311

@@ -2407,6 +2411,8 @@
24072411

24082412
"zustand-hoist": ["[email protected]", "", { "peerDependencies": { "zustand": ">=4.0.0" } }, "sha512-Lhvv3RlLQx1NSUtuhk8jegXe1Wyav9RAOnLd4CRs1SbB5qcFoarAGQTE43vIxXizrm1UQJl1q5uRbOZuXGXGpQ=="],
24092413

2414+
"@anthropic-ai/sdk/@types/node": ["@types/[email protected]", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-+BRgt0G5gYjTvdLac9sIeE0iZcJxi4Jc4PV5EUzqi+88jmQLr+fRZdv2tCTV7IHKSGxM6SaLoOXQWWUiLUItMw=="],
2415+
24102416
"@babel/core/debug": ["[email protected]", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
24112417

24122418
"@babel/core/semver": ["[email protected]", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
@@ -2817,6 +2823,8 @@
28172823

28182824
"yargs/string-width": ["[email protected]", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
28192825

2826+
"@anthropic-ai/sdk/@types/node/undici-types": ["[email protected]", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
2827+
28202828
"@babel/helper-annotate-as-pure/@babel/types/@babel/helper-string-parser": ["@babel/[email protected]", "", {}, "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA=="],
28212829

28222830
"@babel/helper-annotate-as-pure/@babel/types/@babel/helper-validator-identifier": ["@babel/[email protected]", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="],
@@ -3097,12 +3105,16 @@
30973105

30983106
"@types/serve-static/@types/node/undici-types": ["[email protected]", "", {}, "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="],
30993107

3108+
"@types/ws/@types/node/undici-types": ["[email protected]", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
3109+
31003110
"@vercel/nft/glob/minimatch": ["[email protected]", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
31013111

31023112
"ajv-formats/ajv/json-schema-traverse": ["[email protected]", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
31033113

31043114
"body-parser/debug/ms": ["[email protected]", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
31053115

3116+
"bun-types/@types/node/undici-types": ["[email protected]", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
3117+
31063118
"circuit-json-to-readable-netlist/@tscircuit/core/@tscircuit/footprinter": ["@tscircuit/[email protected]", "", { "dependencies": { "@tscircuit/mm": "^0.0.8", "zod": "^3.23.8" }, "peerDependencies": { "circuit-json": "*" } }, "sha512-LeqjpXqPwR++kcshlfe0E3IOsv0Y9BVRjIllDaHFA2OM+gJ91z/SS3CdweXB+qtF4t9G+8MLKf4nU5L1HDGjmg=="],
31073119

31083120
"circuit-json-to-readable-netlist/@tscircuit/core/@tscircuit/math-utils": ["@tscircuit/[email protected]", "", { "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-sPzfXndijet8z29X6f5vnSZddiso2tRg7m6rB+268bVj60mxnxUMD14rKuMlLn6n84fMOpD/X7pRTZUfi6M+Tg=="],
@@ -3463,6 +3475,8 @@
34633475

34643476
"@vercel/nft/glob/minimatch/brace-expansion": ["[email protected]", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="],
34653477

3478+
"circuit-to-svg/bun-types/@types/node/undici-types": ["[email protected]", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
3479+
34663480
"glob-promise/glob/minimatch/brace-expansion": ["[email protected]", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="],
34673481

34683482
"pkg-dir/find-up/locate-path/p-locate": ["[email protected]", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
"circuit-json-to-bom-csv": "^0.0.6",
8181
"circuit-json-to-gerber": "^0.0.16",
8282
"circuit-json-to-pnp-csv": "^0.0.6",
83+
"circuit-json-to-tscircuit": "^0.0.4",
8384
"circuit-json-to-readable-netlist": "^0.0.7",
8485
"class-variance-authority": "^0.7.0",
8586
"clsx": "^2.1.1",
@@ -135,6 +136,7 @@
135136
"@types/babel__standalone": "^7.1.7",
136137
"@types/bun": "^1.1.10",
137138
"@types/country-list": "^2.1.4",
139+
"@types/node": "^22.13.0",
138140
"@types/prismjs": "^1.26.4",
139141
"@types/react": "^18.3.9",
140142
"@types/react-dom": "^18.3.0",
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import { test, expect } from "@playwright/test"
2+
import { exampleCircuitJson } from "./exampleCircuitJson"
3+
4+
async function loginToSite(page) {
5+
const loginButton = page.getByRole("button", { name: "Log in" })
6+
if (await loginButton.isVisible()) {
7+
await loginButton.click()
8+
await page.waitForLoadState("networkidle")
9+
}
10+
}
11+
12+
test.beforeEach(async ({ page }) => {
13+
await page.goto("http://127.0.0.1:5177/quickstart")
14+
await page.waitForTimeout(3000)
15+
await loginToSite(page).catch(() => {})
16+
})
17+
18+
test("should open and close the Circuit Json Import Dialog", async ({
19+
page,
20+
}) => {
21+
const importButton = page.locator('button:has-text("Import Circuit JSON")')
22+
await importButton.click()
23+
24+
const dialog = page.getByRole("dialog")
25+
await expect(dialog).toBeVisible()
26+
27+
const closeButton = dialog.getByRole("button", { name: "Close" })
28+
await closeButton.click()
29+
30+
await expect(dialog).not.toBeVisible()
31+
})
32+
33+
test("should handle valid Circuit JSON input", async ({ page }) => {
34+
const importButton = page.getByRole("button", { name: "Import Circuit JSON" })
35+
await importButton.click()
36+
const textarea = page.locator(
37+
'textarea[placeholder="Paste the Circuit JSON."]',
38+
)
39+
await textarea.fill(JSON.stringify(exampleCircuitJson))
40+
41+
const importDialogButton = page.getByRole("button", { name: "Import" })
42+
await importDialogButton.click()
43+
44+
const successToast = page.locator(
45+
'div.text-sm.font-semibold:has-text("Import Successful")',
46+
)
47+
await successToast.waitFor({ state: "visible", timeout: 5000 })
48+
await expect(successToast).toBeVisible()
49+
})
50+
51+
test("should handle valid Circuit JSON file upload", async ({ page }) => {
52+
const importButton = page.locator('button:has-text("Import Circuit JSON")')
53+
await importButton.click()
54+
55+
const fileInput = page.locator('input[type="file"]')
56+
57+
await fileInput.setInputFiles({
58+
name: "circuit.json",
59+
mimeType: "application/json",
60+
// @ts-expect-error didnt add node types to tsconfig
61+
buffer: Buffer.from(JSON.stringify(exampleCircuitJson)),
62+
})
63+
64+
const importDialogButton = page.getByRole("button", { name: "Import" })
65+
await importDialogButton.click()
66+
const successToast = page.locator(
67+
'div.text-sm.font-semibold:has-text("Import Successful")',
68+
)
69+
await successToast.waitFor({ state: "visible", timeout: 5000 })
70+
await expect(successToast).toBeVisible()
71+
})
72+
73+
test("should handle invalid Circuit JSON input", async ({ page }) => {
74+
const importButton = page.locator('button:has-text("Import Circuit JSON")')
75+
await importButton.click()
76+
77+
const textarea = page.locator(
78+
'textarea[placeholder="Paste the Circuit JSON."]',
79+
)
80+
await textarea.fill("invalid json content")
81+
82+
const importDialogButton = page.getByRole("button", { name: "Import" })
83+
await importDialogButton.click()
84+
85+
const errorToast = page.locator(
86+
'div.text-sm.font-semibold:has-text("Invalid Input")',
87+
)
88+
await errorToast.waitFor({ state: "visible", timeout: 5000 })
89+
await expect(errorToast).toBeVisible()
90+
})
91+
92+
test("should handle invalid Circuit JSON file upload", async ({ page }) => {
93+
const importButton = page.locator('button:has-text("Import Circuit JSON")')
94+
await importButton.click()
95+
96+
const fileInput = page.locator('input[type="file"]')
97+
await fileInput.setInputFiles({
98+
name: "circuit.json",
99+
mimeType: "application/json",
100+
// @ts-expect-error didnt add node types to tsconfig
101+
buffer: Buffer.from(JSON.stringify({})),
102+
})
103+
104+
const importDialogButton = page.getByRole("button", { name: "Import" })
105+
await importDialogButton.click()
106+
107+
const errorToast = page.locator(
108+
'div.text-sm.font-semibold:has-text("Import Failed")',
109+
)
110+
await errorToast.waitFor({ state: "visible", timeout: 5000 })
111+
await expect(errorToast).toBeVisible()
112+
})
113+
114+
test("should handle non-JSON file upload", async ({ page }) => {
115+
const importButton = page.locator('button:has-text("Import Circuit JSON")')
116+
await importButton.click()
117+
118+
const fileInput = page.locator('input[type="file"]')
119+
await fileInput.setInputFiles({
120+
name: "circuit.txt",
121+
mimeType: "application/text",
122+
// @ts-expect-error didnt add node types to tsconfig
123+
buffer: Buffer.from(""),
124+
})
125+
126+
const importDialogButton = page.getByRole("button", { name: "Import" })
127+
await importDialogButton.click()
128+
129+
const errorToast = page.locator(
130+
'div.pb-4 > p:has-text("Please select a valid JSON file.")',
131+
)
132+
await expect(errorToast).toBeVisible()
133+
})

0 commit comments

Comments
 (0)