@tscircuit/fake-snippets 0.0.3 → 0.0.5
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/.github/workflows/bundle-size-analysis.yml +2 -2
- package/bun-tests/fake-snippets-api/routes/package_releases/create.test.ts +131 -0
- package/bun-tests/fake-snippets-api/routes/package_releases/get.test.ts +89 -0
- package/bun-tests/fake-snippets-api/routes/package_releases/list.test.ts +157 -0
- package/bun-tests/fake-snippets-api/routes/package_releases/update.test.ts +189 -0
- package/bun-tests/fake-snippets-api/routes/packages/create.test.ts +26 -0
- package/bun-tests/fake-snippets-api/routes/packages/delete.test.ts +100 -0
- package/bun-tests/fake-snippets-api/routes/packages/get.test.ts +59 -0
- package/bun-tests/fake-snippets-api/routes/packages/list.test.ts +167 -0
- package/bun.lock +3514 -0
- package/dist/bundle.js +741 -108
- package/fake-snippets-api/lib/db/db-client.ts +79 -2
- package/fake-snippets-api/lib/db/schema.ts +55 -0
- package/fake-snippets-api/lib/public-mapping/public-map-package-release.ts +10 -0
- package/fake-snippets-api/lib/public-mapping/public-map-package.ts +31 -0
- package/fake-snippets-api/lib/with-winter-spec.ts +1 -0
- package/fake-snippets-api/routes/api/package_files/download.ts +170 -0
- package/fake-snippets-api/routes/api/package_files/get.ts +52 -0
- package/fake-snippets-api/routes/api/package_files/list.ts +60 -0
- package/fake-snippets-api/routes/api/package_releases/create.ts +83 -0
- package/fake-snippets-api/routes/api/package_releases/get.ts +34 -0
- package/fake-snippets-api/routes/api/package_releases/list.ts +77 -0
- package/fake-snippets-api/routes/api/package_releases/update.ts +96 -0
- package/fake-snippets-api/routes/api/packages/create.ts +58 -0
- package/fake-snippets-api/routes/api/packages/delete.ts +43 -0
- package/fake-snippets-api/routes/api/packages/get.ts +36 -0
- package/fake-snippets-api/routes/api/packages/list.ts +56 -0
- package/fake-snippets-api/routes/api/snippets/create.ts +5 -3
- package/package.json +12 -9
- package/playwright-tests/circuit-json-import.spec.ts +133 -0
- package/playwright-tests/cmd-click.spec.ts +1 -1
- package/playwright-tests/editor-page.spec.ts +1 -1
- package/playwright-tests/exampleCircuitJson.ts +498 -0
- package/playwright-tests/preview-page.spec.ts +2 -9
- package/playwright-tests/snapshots/cmd-click.spec.ts-underlined-imports.png +0 -0
- package/playwright-tests/snapshots/editor-page.spec.ts-editor-with-snippet.png +0 -0
- package/playwright-tests/snapshots/preview-page.spec.ts-preview-snippet-pcb.png +0 -0
- package/src/components/CircuitJsonImportDialog.tsx +186 -0
- package/src/components/CodeAndPreview.tsx +60 -2
- package/src/components/EditorNav.tsx +23 -2
- package/src/components/FootprintDialog.tsx +3 -6
- package/src/components/Header2.tsx +7 -0
- package/src/components/PrefetchPageLink.tsx +4 -1
- package/src/components/SuspenseRunFrame.tsx +16 -0
- package/src/components/dialogs/import-snippet-dialog.tsx +12 -8
- package/src/hooks/use-debounce.ts +17 -0
- package/src/hooks/use-global-store.ts +5 -0
- package/src/hooks/use-run-tsx/index.tsx +1 -0
- package/src/pages/landing.tsx +2 -2
- package/src/pages/preview.tsx +2 -28
- package/src/pages/quickstart.tsx +24 -0
- package/vite.config.ts +1 -1
- package/bun.lockb +0 -0
- package/playwright-tests/snapshots/preview-page.spec.ts-preview-snippet-schematic.png +0 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { packageReleaseSchema } from "fake-snippets-api/lib/db/schema"
|
|
2
|
+
import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
|
|
3
|
+
import { publicMapPackageRelease } from "fake-snippets-api/lib/public-mapping/public-map-package-release"
|
|
4
|
+
import { z } from "zod"
|
|
5
|
+
|
|
6
|
+
export default withRouteSpec({
|
|
7
|
+
methods: ["POST"],
|
|
8
|
+
auth: "none",
|
|
9
|
+
jsonBody: z
|
|
10
|
+
.object({
|
|
11
|
+
package_id: z.string().optional(),
|
|
12
|
+
package_name: z.string().optional(),
|
|
13
|
+
is_latest: z.boolean().optional(),
|
|
14
|
+
version: z.string().optional(),
|
|
15
|
+
commit_sha: z.string().optional(),
|
|
16
|
+
})
|
|
17
|
+
.refine(({ package_id, package_name }) => {
|
|
18
|
+
if (package_id && package_name) {
|
|
19
|
+
return false
|
|
20
|
+
}
|
|
21
|
+
return true
|
|
22
|
+
}, "package_id and package_name are mutually exclusive")
|
|
23
|
+
.refine(({ package_id, package_name }) => {
|
|
24
|
+
if (!package_id && !package_name) {
|
|
25
|
+
return false
|
|
26
|
+
}
|
|
27
|
+
return true
|
|
28
|
+
}, "package_id or package_name is required"),
|
|
29
|
+
jsonResponse: z.object({
|
|
30
|
+
ok: z.boolean(),
|
|
31
|
+
package_releases: z.array(packageReleaseSchema),
|
|
32
|
+
}),
|
|
33
|
+
})(async (req, ctx) => {
|
|
34
|
+
const { package_id, package_name, is_latest, version, commit_sha } =
|
|
35
|
+
req.jsonBody
|
|
36
|
+
|
|
37
|
+
if (!package_id && !package_name && !is_latest && !version && !commit_sha) {
|
|
38
|
+
return ctx.error(400, {
|
|
39
|
+
error_code: "invalid_query",
|
|
40
|
+
message:
|
|
41
|
+
"At least one of package_id, package_name, is_latest, version or commit_sha is required",
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let releases = ctx.db.packageReleases
|
|
46
|
+
|
|
47
|
+
// Apply filters
|
|
48
|
+
if (package_id) {
|
|
49
|
+
releases = releases.filter((pr) => pr.package_id === package_id)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (package_name) {
|
|
53
|
+
const pkg = ctx.db.packages.find((p) => p.name === package_name)
|
|
54
|
+
if (pkg) {
|
|
55
|
+
releases = releases.filter((pr) => pr.package_id === pkg.package_id)
|
|
56
|
+
} else {
|
|
57
|
+
releases = []
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (is_latest !== undefined) {
|
|
62
|
+
releases = releases.filter((pr) => pr.is_latest === is_latest)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (version) {
|
|
66
|
+
releases = releases.filter((pr) => pr.version === version)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (commit_sha) {
|
|
70
|
+
releases = releases.filter((pr) => pr.commit_sha === commit_sha)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return ctx.json({
|
|
74
|
+
ok: true,
|
|
75
|
+
package_releases: releases.map((pr) => publicMapPackageRelease(pr)),
|
|
76
|
+
})
|
|
77
|
+
})
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
|
|
2
|
+
import { z } from "zod"
|
|
3
|
+
|
|
4
|
+
export default withRouteSpec({
|
|
5
|
+
methods: ["POST"],
|
|
6
|
+
auth: "none",
|
|
7
|
+
jsonBody: z.object({
|
|
8
|
+
package_release_id: z.string().optional(),
|
|
9
|
+
package_name_with_version: z.string().optional(),
|
|
10
|
+
is_locked: z.boolean().optional(),
|
|
11
|
+
is_latest: z.boolean().optional(),
|
|
12
|
+
license: z.string().optional(),
|
|
13
|
+
}),
|
|
14
|
+
jsonResponse: z.object({
|
|
15
|
+
ok: z.boolean(),
|
|
16
|
+
}),
|
|
17
|
+
})(async (req, ctx) => {
|
|
18
|
+
const {
|
|
19
|
+
package_release_id,
|
|
20
|
+
package_name_with_version,
|
|
21
|
+
is_locked,
|
|
22
|
+
is_latest,
|
|
23
|
+
license,
|
|
24
|
+
} = req.jsonBody
|
|
25
|
+
let releaseId = package_release_id
|
|
26
|
+
|
|
27
|
+
// Handle package_name_with_version lookup
|
|
28
|
+
if (!releaseId && package_name_with_version) {
|
|
29
|
+
const [packageName, version] = package_name_with_version.split("@")
|
|
30
|
+
const pkg = ctx.db.packages.find((p) => p.name === packageName)
|
|
31
|
+
if (pkg) {
|
|
32
|
+
const release = ctx.db.packageReleases.find(
|
|
33
|
+
(pr) => pr.package_id === pkg.package_id && pr.version === version,
|
|
34
|
+
)
|
|
35
|
+
if (release) {
|
|
36
|
+
releaseId = release.package_release_id
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!releaseId) {
|
|
42
|
+
return ctx.error(404, {
|
|
43
|
+
error_code: "package_release_not_found",
|
|
44
|
+
message: "Package release not found",
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const delta = { is_locked, is_latest, license }
|
|
49
|
+
if (
|
|
50
|
+
Object.keys(delta).filter(
|
|
51
|
+
(k) => delta[k as keyof typeof delta] !== undefined,
|
|
52
|
+
).length === 0
|
|
53
|
+
) {
|
|
54
|
+
return ctx.error(400, {
|
|
55
|
+
error_code: "no_fields_provided",
|
|
56
|
+
message: "No fields provided to update",
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const release = ctx.db.packageReleases.find(
|
|
61
|
+
(pr) => pr.package_release_id === releaseId,
|
|
62
|
+
)
|
|
63
|
+
if (!release) {
|
|
64
|
+
return ctx.error(404, {
|
|
65
|
+
error_code: "package_release_not_found",
|
|
66
|
+
message: "Package release not found",
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Handle is_latest updates
|
|
71
|
+
if (is_latest !== undefined && is_latest) {
|
|
72
|
+
ctx.db.packageReleases
|
|
73
|
+
.filter(
|
|
74
|
+
(pr) =>
|
|
75
|
+
pr.package_id === release.package_id &&
|
|
76
|
+
pr.package_release_id !== releaseId &&
|
|
77
|
+
pr.is_latest,
|
|
78
|
+
)
|
|
79
|
+
.forEach((pr) => {
|
|
80
|
+
pr.is_latest = false
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Update the release
|
|
85
|
+
Object.assign(release, {
|
|
86
|
+
...(is_locked !== undefined && { is_locked }),
|
|
87
|
+
...(is_latest !== undefined && { is_latest }),
|
|
88
|
+
...(license !== undefined && { license }),
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
ctx.db.updatePackageRelease(release)
|
|
92
|
+
|
|
93
|
+
return ctx.json({
|
|
94
|
+
ok: true,
|
|
95
|
+
})
|
|
96
|
+
})
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { packageSchema } from "fake-snippets-api/lib/db/schema"
|
|
2
|
+
import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
|
|
3
|
+
import { publicMapPackage } from "fake-snippets-api/lib/public-mapping/public-map-package"
|
|
4
|
+
import { z } from "zod"
|
|
5
|
+
|
|
6
|
+
export default withRouteSpec({
|
|
7
|
+
methods: ["POST"],
|
|
8
|
+
auth: "session",
|
|
9
|
+
jsonBody: z.object({
|
|
10
|
+
name: z
|
|
11
|
+
.string()
|
|
12
|
+
.regex(
|
|
13
|
+
/^[@a-zA-Z0-9-_\/]+$/,
|
|
14
|
+
"Package name can only contain letters, numbers, hyphens, underscores, and forward slashes",
|
|
15
|
+
)
|
|
16
|
+
.transform((name) => name.replace(/^@/, "")),
|
|
17
|
+
description: z.string().optional(),
|
|
18
|
+
}),
|
|
19
|
+
jsonResponse: z.object({
|
|
20
|
+
package: packageSchema.optional(),
|
|
21
|
+
}),
|
|
22
|
+
})(async (req, ctx) => {
|
|
23
|
+
const { name, description } = req.jsonBody
|
|
24
|
+
|
|
25
|
+
const existingPackage = ctx.db.packages.find((pkg) => pkg.name === name)
|
|
26
|
+
|
|
27
|
+
if (existingPackage) {
|
|
28
|
+
throw ctx.error(400, {
|
|
29
|
+
error_code: "package_already_exists",
|
|
30
|
+
message: "A package with this name already exists",
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const newPackage = ctx.db.addPackage({
|
|
35
|
+
name,
|
|
36
|
+
description: description ?? null,
|
|
37
|
+
creator_account_id: ctx.auth.account_id,
|
|
38
|
+
owner_org_id: ctx.auth.personal_org_id,
|
|
39
|
+
owner_github_username: ctx.auth.github_username,
|
|
40
|
+
latest_package_release_id: null,
|
|
41
|
+
latest_version: null,
|
|
42
|
+
license: null,
|
|
43
|
+
is_source_from_github: false,
|
|
44
|
+
created_at: new Date().toISOString(),
|
|
45
|
+
updated_at: new Date().toISOString(),
|
|
46
|
+
unscoped_name: name,
|
|
47
|
+
star_count: 0,
|
|
48
|
+
ai_description: name,
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
if (!newPackage) {
|
|
52
|
+
throw new Error("Failed to create package")
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return ctx.json({
|
|
56
|
+
package: publicMapPackage(newPackage),
|
|
57
|
+
})
|
|
58
|
+
})
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
|
|
2
|
+
import { z } from "zod"
|
|
3
|
+
|
|
4
|
+
export default withRouteSpec({
|
|
5
|
+
methods: ["POST"],
|
|
6
|
+
auth: "session",
|
|
7
|
+
jsonBody: z.object({
|
|
8
|
+
package_id: z.string(),
|
|
9
|
+
}),
|
|
10
|
+
jsonResponse: z.object({
|
|
11
|
+
ok: z.boolean(),
|
|
12
|
+
}),
|
|
13
|
+
})(async (req, ctx) => {
|
|
14
|
+
const { package_id } = req.jsonBody
|
|
15
|
+
|
|
16
|
+
const packageIndex = ctx.db.packages.findIndex(
|
|
17
|
+
(p) => p.package_id === package_id,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
if (packageIndex === -1) {
|
|
21
|
+
return ctx.error(404, {
|
|
22
|
+
error_code: "package_not_found",
|
|
23
|
+
message: "Package not found",
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const pkg = ctx.db.packages[packageIndex]
|
|
28
|
+
|
|
29
|
+
console.log("pkg", pkg.owner_org_id, ctx.auth.personal_org_id)
|
|
30
|
+
|
|
31
|
+
if (pkg.owner_org_id !== ctx.auth.personal_org_id) {
|
|
32
|
+
return ctx.error(403, {
|
|
33
|
+
error_code: "forbidden",
|
|
34
|
+
message: "You don't have permission to delete this package",
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
ctx.db.packages.splice(packageIndex, 1)
|
|
39
|
+
|
|
40
|
+
return ctx.json({
|
|
41
|
+
ok: true,
|
|
42
|
+
})
|
|
43
|
+
})
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { packageSchema } from "fake-snippets-api/lib/db/schema"
|
|
2
|
+
import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
|
|
3
|
+
import { publicMapPackage } from "fake-snippets-api/lib/public-mapping/public-map-package"
|
|
4
|
+
import { z } from "zod"
|
|
5
|
+
|
|
6
|
+
export default withRouteSpec({
|
|
7
|
+
methods: ["GET", "POST"],
|
|
8
|
+
auth: "optional_session",
|
|
9
|
+
commonParams: z.object({
|
|
10
|
+
package_id: z.string().optional(),
|
|
11
|
+
name: z.string().optional(),
|
|
12
|
+
}),
|
|
13
|
+
jsonBody: z.any().optional(),
|
|
14
|
+
jsonResponse: z.object({
|
|
15
|
+
ok: z.boolean(),
|
|
16
|
+
package: packageSchema.optional(),
|
|
17
|
+
}),
|
|
18
|
+
})(async (req, ctx) => {
|
|
19
|
+
const { package_id, name } = req.commonParams
|
|
20
|
+
|
|
21
|
+
const foundPackage =
|
|
22
|
+
(package_id && ctx.db.getPackageById(package_id)) ||
|
|
23
|
+
ctx.db.packages.find((p) => p.name === name)
|
|
24
|
+
|
|
25
|
+
if (!foundPackage) {
|
|
26
|
+
return ctx.error(404, {
|
|
27
|
+
error_code: "package_not_found",
|
|
28
|
+
message: `Package not found (searched using ${JSON.stringify(req.commonParams)})`,
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return ctx.json({
|
|
33
|
+
ok: true,
|
|
34
|
+
package: publicMapPackage(foundPackage),
|
|
35
|
+
})
|
|
36
|
+
})
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { packageSchema } from "fake-snippets-api/lib/db/schema"
|
|
2
|
+
import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
|
|
3
|
+
import { z } from "zod"
|
|
4
|
+
|
|
5
|
+
export default withRouteSpec({
|
|
6
|
+
methods: ["GET", "POST"],
|
|
7
|
+
auth: "optional_session",
|
|
8
|
+
commonParams: z.object({
|
|
9
|
+
creator_account_id: z.string().optional(),
|
|
10
|
+
owner_github_username: z.string().optional(),
|
|
11
|
+
is_writable: z.boolean().optional(),
|
|
12
|
+
name: z.string().optional(),
|
|
13
|
+
}),
|
|
14
|
+
jsonResponse: z.object({
|
|
15
|
+
ok: z.boolean(),
|
|
16
|
+
packages: z.array(packageSchema),
|
|
17
|
+
}),
|
|
18
|
+
})(async (req, ctx) => {
|
|
19
|
+
const { creator_account_id, owner_github_username, name, is_writable } =
|
|
20
|
+
req.commonParams
|
|
21
|
+
|
|
22
|
+
const auth = "auth" in ctx && ctx.auth ? ctx.auth : null
|
|
23
|
+
|
|
24
|
+
if (!auth && !is_writable && !creator_account_id && !owner_github_username) {
|
|
25
|
+
return ctx.error(400, {
|
|
26
|
+
error_code: "invalid_request",
|
|
27
|
+
message: "You must provide some filtering parameters or be logged in",
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let packages = ctx.db.packages
|
|
32
|
+
|
|
33
|
+
// Apply filters
|
|
34
|
+
if (creator_account_id) {
|
|
35
|
+
packages = packages.filter(
|
|
36
|
+
(p) => p.creator_account_id === creator_account_id,
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
if (owner_github_username) {
|
|
40
|
+
packages = packages.filter(
|
|
41
|
+
(p) => p.owner_github_username === owner_github_username,
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
if (name) {
|
|
45
|
+
packages = packages.filter((p) => p.name === name)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (is_writable && auth) {
|
|
49
|
+
packages = packages.filter((p) => p.owner_org_id === auth.personal_org_id)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return ctx.json({
|
|
53
|
+
ok: true,
|
|
54
|
+
packages,
|
|
55
|
+
})
|
|
56
|
+
})
|
|
@@ -46,8 +46,10 @@ export default withRouteSpec({
|
|
|
46
46
|
})
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
let newSnippet: Omit<
|
|
50
|
+
z.input<typeof snippetSchema>,
|
|
51
|
+
"snippet_id" | "package_release_id"
|
|
52
|
+
> = {
|
|
51
53
|
name: `${ctx.auth.github_username}/${unscoped_name}`,
|
|
52
54
|
unscoped_name,
|
|
53
55
|
owner_name: ctx.auth.github_username,
|
|
@@ -62,7 +64,7 @@ export default withRouteSpec({
|
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
try {
|
|
65
|
-
ctx.db.addSnippet(newSnippet)
|
|
67
|
+
newSnippet = ctx.db.addSnippet(newSnippet)
|
|
66
68
|
} catch (error) {
|
|
67
69
|
return ctx.error(500, {
|
|
68
70
|
error_code: "snippet_creation_failed",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tscircuit/fake-snippets",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -61,13 +61,13 @@
|
|
|
61
61
|
"@radix-ui/react-toggle": "^1.1.0",
|
|
62
62
|
"@radix-ui/react-toggle-group": "^1.1.0",
|
|
63
63
|
"@radix-ui/react-tooltip": "^1.1.2",
|
|
64
|
-
"@tscircuit/3d-viewer": "^0.0.
|
|
65
|
-
"@tscircuit/footprinter": "^0.0.
|
|
64
|
+
"@tscircuit/3d-viewer": "^0.0.113",
|
|
65
|
+
"@tscircuit/footprinter": "^0.0.102",
|
|
66
66
|
"@tscircuit/layout": "^0.0.29",
|
|
67
67
|
"@tscircuit/math-utils": "^0.0.10",
|
|
68
68
|
"@tscircuit/mm": "^0.0.8",
|
|
69
69
|
"@tscircuit/pcb-viewer": "^1.11.12",
|
|
70
|
-
"@tscircuit/props": "^0.0.
|
|
70
|
+
"@tscircuit/props": "^0.0.138",
|
|
71
71
|
"@tscircuit/schematic-viewer": "^1.4.3",
|
|
72
72
|
"@types/file-saver": "^2.0.7",
|
|
73
73
|
"@types/ms": "^0.7.34",
|
|
@@ -76,18 +76,19 @@
|
|
|
76
76
|
"@valtown/codemirror-ts": "^2.2.0",
|
|
77
77
|
"@vercel/analytics": "^1.4.1",
|
|
78
78
|
"change-case": "^5.4.4",
|
|
79
|
-
"circuit-json": "^0.0.
|
|
79
|
+
"circuit-json": "^0.0.135",
|
|
80
80
|
"circuit-json-to-bom-csv": "^0.0.6",
|
|
81
81
|
"circuit-json-to-gerber": "^0.0.16",
|
|
82
82
|
"circuit-json-to-pnp-csv": "^0.0.6",
|
|
83
|
-
"circuit-json-to-
|
|
83
|
+
"circuit-json-to-tscircuit": "^0.0.4",
|
|
84
|
+
"circuit-json-to-readable-netlist": "^0.0.7",
|
|
84
85
|
"class-variance-authority": "^0.7.0",
|
|
85
86
|
"clsx": "^2.1.1",
|
|
86
87
|
"cmdk": "^1.0.4",
|
|
87
88
|
"codemirror": "^6.0.1",
|
|
88
89
|
"country-list": "^2.3.0",
|
|
89
90
|
"date-fns": "^4.1.0",
|
|
90
|
-
"dsn-converter": "^0.0.
|
|
91
|
+
"dsn-converter": "^0.0.60",
|
|
91
92
|
"easyeda": "^0.0.62",
|
|
92
93
|
"embla-carousel-react": "^8.3.0",
|
|
93
94
|
"extract-codefence": "^0.0.4",
|
|
@@ -129,11 +130,13 @@
|
|
|
129
130
|
"@babel/standalone": "^7.26.2",
|
|
130
131
|
"@biomejs/biome": "^1.9.2",
|
|
131
132
|
"@playwright/test": "^1.48.0",
|
|
132
|
-
"@tscircuit/core": "^0.0.
|
|
133
|
-
"@tscircuit/prompt-benchmarks": "^0.0.
|
|
133
|
+
"@tscircuit/core": "^0.0.296",
|
|
134
|
+
"@tscircuit/prompt-benchmarks": "^0.0.28",
|
|
135
|
+
"@tscircuit/runframe": "^0.0.139",
|
|
134
136
|
"@types/babel__standalone": "^7.1.7",
|
|
135
137
|
"@types/bun": "^1.1.10",
|
|
136
138
|
"@types/country-list": "^2.1.4",
|
|
139
|
+
"@types/node": "^22.13.0",
|
|
137
140
|
"@types/prismjs": "^1.26.4",
|
|
138
141
|
"@types/react": "^18.3.9",
|
|
139
142
|
"@types/react-dom": "^18.3.0",
|
|
@@ -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
|
+
})
|
|
@@ -3,7 +3,7 @@ import { test, expect } from "@playwright/test"
|
|
|
3
3
|
test("Editor handles imports with underlining and cmd+click", async ({
|
|
4
4
|
page,
|
|
5
5
|
}) => {
|
|
6
|
-
await page.goto("http://127.0.0.1:5177/editor?snippet_id=
|
|
6
|
+
await page.goto("http://127.0.0.1:5177/editor?snippet_id=snippet_3")
|
|
7
7
|
|
|
8
8
|
await page.waitForLoadState("networkidle")
|
|
9
9
|
|
|
@@ -8,7 +8,7 @@ test("Editor loads snippet correctly", async ({ page }) => {
|
|
|
8
8
|
await page.waitForLoadState("networkidle")
|
|
9
9
|
|
|
10
10
|
// Check for specific text that should be present
|
|
11
|
-
await expect(page.getByText("
|
|
11
|
+
await expect(page.getByText("A555Timer", { exact: true })).toBeVisible()
|
|
12
12
|
|
|
13
13
|
// Take a snapshot
|
|
14
14
|
await expect(page).toHaveScreenshot("editor-with-snippet.png")
|