chore: 初始化 BlockFlow Workbench 仓库

建立前端与 Tauri 桌面端的首个版本提交,包含核心编辑器、项目文件读写、测试与构建配置。

补充 Git 忽略规则和换行规范,排除依赖、构建产物、本地运行日志与临时验证文件,方便在其他电脑继续开发。
This commit is contained in:
2026-05-29 17:23:43 +08:00
commit 589ff15213
88 changed files with 31656 additions and 0 deletions

62
tests/editor.test.tsx Normal file
View File

@@ -0,0 +1,62 @@
import "@testing-library/jest-dom/vitest"
import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react"
import { afterEach, beforeEach, describe, expect, it } from "vitest"
import { App } from "../src/app/App"
import { getCurrentRenderResult, useWorkbenchStore } from "../src/app/store/workbenchStore"
describe("BlockFlowEditor", () => {
beforeEach(() => {
useWorkbenchStore.getState().resetDemo()
})
afterEach(() => {
cleanup()
})
it("renders the demo template in the TipTap editor", () => {
render(<App />)
expect(screen.getByTestId("blockflow-editor")).toHaveTextContent("user.name")
expect(screen.getByTestId("blockflow-editor")).toHaveTextContent("fragment_common_footer")
})
it("inserts a variable through the command menu and updates AST", async () => {
render(<App />)
fireEvent.click(screen.getByRole("button", { name: "打开命令菜单" }))
fireEvent.click(screen.getByRole("button", { name: "变量 user.email" }))
await waitFor(() => {
const blocks = useWorkbenchStore.getState().snapshot.templates.template_main?.children ?? []
expect(JSON.stringify(blocks)).toContain("user.email")
expect(useWorkbenchStore.getState().saveStatus).toBe("dirty")
})
})
it("inserts condition and loop blocks through the command menu", async () => {
render(<App />)
fireEvent.click(screen.getByRole("button", { name: "打开命令菜单" }))
fireEvent.click(screen.getByRole("button", { name: "条件 if user.isVip" }))
fireEvent.click(screen.getByRole("button", { name: "打开命令菜单" }))
fireEvent.click(screen.getByRole("button", { name: "循环 for item in items" }))
await waitFor(() => {
const blocks = useWorkbenchStore.getState().snapshot.templates.template_main?.children ?? []
expect(JSON.stringify(blocks)).toContain("\"type\":\"condition\"")
expect(JSON.stringify(blocks)).toContain("\"type\":\"loop\"")
})
})
it("keeps render preview connected to the AST after editing", async () => {
render(<App />)
fireEvent.click(screen.getByRole("button", { name: "打开命令菜单" }))
fireEvent.click(screen.getByRole("button", { name: "变量 user.email" }))
await waitFor(() => {
const result = getCurrentRenderResult(useWorkbenchStore.getState())
expect(result.stats.missingVariables).toContain("user.email")
})
})
})