Skip to content

Commit

Permalink
feat: import circuit json (#599)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
ArnavK-09 authored Feb 2, 2025
1 parent d76f839 commit 36e064b
Show file tree
Hide file tree
Showing 6 changed files with 859 additions and 2 deletions.
18 changes: 16 additions & 2 deletions bun.lock
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"circuit-json-to-gerber": "^0.0.16",
"circuit-json-to-pnp-csv": "^0.0.6",
"circuit-json-to-readable-netlist": "^0.0.7",
"circuit-json-to-tscircuit": "^0.0.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cmdk": "^1.0.4",
Expand Down Expand Up @@ -115,6 +116,7 @@
"@types/babel__standalone": "^7.1.7",
"@types/bun": "^1.1.10",
"@types/country-list": "^2.1.4",
"@types/node": "^22.13.0",
"@types/prismjs": "^1.26.4",
"@types/react": "^18.3.9",
"@types/react-dom": "^18.3.0",
Expand Down Expand Up @@ -805,7 +807,7 @@

"@types/ms": ["@types/ms@0.7.34", "", {}, "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g=="],

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

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

Expand Down Expand Up @@ -1041,6 +1043,8 @@

"circuit-json-to-readable-netlist": ["circuit-json-to-readable-netlist@0.0.7", "", { "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=="],

"circuit-json-to-tscircuit": ["circuit-json-to-tscircuit@0.0.4", "", { "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-LpHbOwdPE4+CooWPAPoKXWs4vxTrjJgu/avnxE3AqGwCD9r0ZnT73mEAB9oQi6T1i7T53zdkSR6y+zpsyCSE7g=="],

"circuit-to-svg": ["circuit-to-svg@0.0.101", "", { "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=="],

"class-variance-authority": ["class-variance-authority@0.7.0", "", { "dependencies": { "clsx": "2.0.0" } }, "sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A=="],
Expand Down Expand Up @@ -2301,7 +2305,7 @@

"uint8array-extras": ["uint8array-extras@1.4.0", "", {}, "sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ=="],

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

"unicorn-magic": ["unicorn-magic@0.1.0", "", {}, "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ=="],

Expand Down Expand Up @@ -2407,6 +2411,8 @@

"zustand-hoist": ["zustand-hoist@2.0.1", "", { "peerDependencies": { "zustand": ">=4.0.0" } }, "sha512-Lhvv3RlLQx1NSUtuhk8jegXe1Wyav9RAOnLd4CRs1SbB5qcFoarAGQTE43vIxXizrm1UQJl1q5uRbOZuXGXGpQ=="],

"@anthropic-ai/sdk/@types/node": ["@types/node@18.19.54", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-+BRgt0G5gYjTvdLac9sIeE0iZcJxi4Jc4PV5EUzqi+88jmQLr+fRZdv2tCTV7IHKSGxM6SaLoOXQWWUiLUItMw=="],

"@babel/core/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],

"@babel/core/semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
Expand Down Expand Up @@ -2817,6 +2823,8 @@

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

"@anthropic-ai/sdk/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],

"@babel/helper-annotate-as-pure/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.25.9", "", {}, "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA=="],

"@babel/helper-annotate-as-pure/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="],
Expand Down Expand Up @@ -3097,12 +3105,16 @@

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

"@types/ws/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],

"@vercel/nft/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],

"ajv-formats/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],

"body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],

"bun-types/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],

"circuit-json-to-readable-netlist/@tscircuit/core/@tscircuit/footprinter": ["@tscircuit/footprinter@0.0.97", "", { "dependencies": { "@tscircuit/mm": "^0.0.8", "zod": "^3.23.8" }, "peerDependencies": { "circuit-json": "*" } }, "sha512-LeqjpXqPwR++kcshlfe0E3IOsv0Y9BVRjIllDaHFA2OM+gJ91z/SS3CdweXB+qtF4t9G+8MLKf4nU5L1HDGjmg=="],

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

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

"circuit-to-svg/bun-types/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],

"glob-promise/glob/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="],

"pkg-dir/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
"circuit-json-to-bom-csv": "^0.0.6",
"circuit-json-to-gerber": "^0.0.16",
"circuit-json-to-pnp-csv": "^0.0.6",
"circuit-json-to-tscircuit": "^0.0.4",
"circuit-json-to-readable-netlist": "^0.0.7",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
Expand Down Expand Up @@ -135,6 +136,7 @@
"@types/babel__standalone": "^7.1.7",
"@types/bun": "^1.1.10",
"@types/country-list": "^2.1.4",
"@types/node": "^22.13.0",
"@types/prismjs": "^1.26.4",
"@types/react": "^18.3.9",
"@types/react-dom": "^18.3.0",
Expand Down
133 changes: 133 additions & 0 deletions playwright-tests/circuit-json-import.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { test, expect } from "@playwright/test"
import { exampleCircuitJson } from "./exampleCircuitJson"

async function loginToSite(page) {
const loginButton = page.getByRole("button", { name: "Log in" })
if (await loginButton.isVisible()) {
await loginButton.click()
await page.waitForLoadState("networkidle")
}
}

test.beforeEach(async ({ page }) => {
await page.goto("http://127.0.0.1:5177/quickstart")
await page.waitForTimeout(3000)
await loginToSite(page).catch(() => {})
})

test("should open and close the Circuit Json Import Dialog", async ({
page,
}) => {
const importButton = page.locator('button:has-text("Import Circuit JSON")')
await importButton.click()

const dialog = page.getByRole("dialog")
await expect(dialog).toBeVisible()

const closeButton = dialog.getByRole("button", { name: "Close" })
await closeButton.click()

await expect(dialog).not.toBeVisible()
})

test("should handle valid Circuit JSON input", async ({ page }) => {
const importButton = page.getByRole("button", { name: "Import Circuit JSON" })
await importButton.click()
const textarea = page.locator(
'textarea[placeholder="Paste the Circuit JSON."]',
)
await textarea.fill(JSON.stringify(exampleCircuitJson))

const importDialogButton = page.getByRole("button", { name: "Import" })
await importDialogButton.click()

const successToast = page.locator(
'div.text-sm.font-semibold:has-text("Import Successful")',
)
await successToast.waitFor({ state: "visible", timeout: 5000 })
await expect(successToast).toBeVisible()
})

test("should handle valid Circuit JSON file upload", async ({ page }) => {
const importButton = page.locator('button:has-text("Import Circuit JSON")')
await importButton.click()

const fileInput = page.locator('input[type="file"]')

await fileInput.setInputFiles({
name: "circuit.json",
mimeType: "application/json",
// @ts-expect-error didnt add node types to tsconfig
buffer: Buffer.from(JSON.stringify(exampleCircuitJson)),
})

const importDialogButton = page.getByRole("button", { name: "Import" })
await importDialogButton.click()
const successToast = page.locator(
'div.text-sm.font-semibold:has-text("Import Successful")',
)
await successToast.waitFor({ state: "visible", timeout: 5000 })
await expect(successToast).toBeVisible()
})

test("should handle invalid Circuit JSON input", async ({ page }) => {
const importButton = page.locator('button:has-text("Import Circuit JSON")')
await importButton.click()

const textarea = page.locator(
'textarea[placeholder="Paste the Circuit JSON."]',
)
await textarea.fill("invalid json content")

const importDialogButton = page.getByRole("button", { name: "Import" })
await importDialogButton.click()

const errorToast = page.locator(
'div.text-sm.font-semibold:has-text("Invalid Input")',
)
await errorToast.waitFor({ state: "visible", timeout: 5000 })
await expect(errorToast).toBeVisible()
})

test("should handle invalid Circuit JSON file upload", async ({ page }) => {
const importButton = page.locator('button:has-text("Import Circuit JSON")')
await importButton.click()

const fileInput = page.locator('input[type="file"]')
await fileInput.setInputFiles({
name: "circuit.json",
mimeType: "application/json",
// @ts-expect-error didnt add node types to tsconfig
buffer: Buffer.from(JSON.stringify({})),
})

const importDialogButton = page.getByRole("button", { name: "Import" })
await importDialogButton.click()

const errorToast = page.locator(
'div.text-sm.font-semibold:has-text("Import Failed")',
)
await errorToast.waitFor({ state: "visible", timeout: 5000 })
await expect(errorToast).toBeVisible()
})

test("should handle non-JSON file upload", async ({ page }) => {
const importButton = page.locator('button:has-text("Import Circuit JSON")')
await importButton.click()

const fileInput = page.locator('input[type="file"]')
await fileInput.setInputFiles({
name: "circuit.txt",
mimeType: "application/text",
// @ts-expect-error didnt add node types to tsconfig
buffer: Buffer.from(""),
})

const importDialogButton = page.getByRole("button", { name: "Import" })
await importDialogButton.click()

const errorToast = page.locator(
'div.pb-4 > p:has-text("Please select a valid JSON file.")',
)
await expect(errorToast).toBeVisible()
})
Loading

0 comments on commit 36e064b

Please sign in to comment.