jac-client 0.2.8__py3-none-any.whl → 0.2.11__py3-none-any.whl
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.
- jac_client/examples/all-in-one/button.jac +4 -3
- jac_client/examples/all-in-one/components/CategoryFilter.jac +36 -24
- jac_client/examples/all-in-one/components/Header.jac +12 -8
- jac_client/examples/all-in-one/components/ProfitOverview.jac +49 -35
- jac_client/examples/all-in-one/components/Summary.jac +59 -36
- jac_client/examples/all-in-one/components/TransactionForm.jac +142 -112
- jac_client/examples/all-in-one/components/TransactionItem.jac +37 -30
- jac_client/examples/all-in-one/components/TransactionList.jac +33 -26
- jac_client/examples/all-in-one/components/button.jac +4 -3
- jac_client/examples/all-in-one/components/navigation.jac +111 -117
- jac_client/examples/all-in-one/constants/categories.jac +23 -24
- jac_client/examples/all-in-one/constants/clients.jac +7 -8
- jac_client/examples/all-in-one/context/BudgetContext.jac +9 -6
- jac_client/examples/all-in-one/hooks/useBudget.jac +18 -12
- jac_client/examples/all-in-one/hooks/useLocalStorage.jac +14 -13
- jac_client/examples/all-in-one/main.jac +542 -0
- jac_client/examples/all-in-one/pages/BudgetPlanner.jac +26 -12
- jac_client/examples/all-in-one/pages/FeaturesTest.jac +43 -12
- jac_client/examples/all-in-one/pages/LandingPage.jac +113 -90
- jac_client/examples/all-in-one/pages/budget_planner_ui.cl.jac +65 -0
- jac_client/examples/all-in-one/pages/features_test_ui.cl.jac +675 -0
- jac_client/examples/all-in-one/pages/loginPage.jac +114 -119
- jac_client/examples/all-in-one/pages/nestedDemo.jac +44 -51
- jac_client/examples/all-in-one/pages/notFound.jac +15 -21
- jac_client/examples/all-in-one/pages/signupPage.jac +113 -119
- jac_client/examples/all-in-one/utils/formatters.jac +5 -8
- jac_client/examples/asset-serving/css-with-image/main.jac +92 -0
- jac_client/examples/asset-serving/image-asset/main.jac +56 -0
- jac_client/examples/asset-serving/import-alias/main.jac +109 -0
- jac_client/examples/basic/main.jac +23 -0
- jac_client/examples/basic-auth/main.jac +363 -0
- jac_client/examples/basic-auth-with-router/main.jac +451 -0
- jac_client/examples/basic-full-stack/main.jac +362 -0
- jac_client/examples/css-styling/js-styling/main.jac +63 -0
- jac_client/examples/css-styling/material-ui/main.jac +122 -0
- jac_client/examples/css-styling/pure-css/main.jac +55 -0
- jac_client/examples/css-styling/sass-example/main.jac +55 -0
- jac_client/examples/css-styling/styled-components/main.jac +62 -0
- jac_client/examples/css-styling/tailwind-example/main.jac +74 -0
- jac_client/examples/full-stack-with-auth/main.jac +696 -0
- jac_client/examples/little-x/main.jac +681 -0
- jac_client/examples/little-x/src/submit-button.jac +15 -14
- jac_client/examples/nested-folders/nested-advance/main.jac +26 -0
- jac_client/examples/nested-folders/nested-advance/src/ButtonRoot.jac +4 -6
- jac_client/examples/nested-folders/nested-advance/src/level1/ButtonSecondL.jac +9 -13
- jac_client/examples/nested-folders/nested-advance/src/level1/Card.jac +29 -32
- jac_client/examples/nested-folders/nested-advance/src/level1/level2/ButtonThirdL.jac +12 -18
- jac_client/examples/nested-folders/nested-basic/{src/app.jac → main.jac} +7 -5
- jac_client/examples/nested-folders/nested-basic/src/button.jac +4 -3
- jac_client/examples/nested-folders/nested-basic/src/components/button.jac +4 -3
- jac_client/examples/ts-support/main.jac +35 -0
- jac_client/examples/with-router/main.jac +286 -0
- jac_client/plugin/cli.jac +491 -411
- jac_client/plugin/client.jac +25 -0
- jac_client/plugin/client_runtime.cl.jac +10 -4
- jac_client/plugin/impl/client.impl.jac +96 -55
- jac_client/plugin/impl/client_runtime.impl.jac +155 -1
- jac_client/plugin/plugin_config.jac +211 -29
- jac_client/plugin/src/__init__.jac +0 -2
- jac_client/plugin/src/compiler.jac +0 -1
- jac_client/plugin/src/config_loader.jac +1 -0
- jac_client/plugin/src/desktop_config.jac +31 -0
- jac_client/plugin/src/impl/compiler.impl.jac +49 -17
- jac_client/plugin/src/impl/config_loader.impl.jac +8 -0
- jac_client/plugin/src/impl/desktop_config.impl.jac +191 -0
- jac_client/plugin/src/impl/jac_to_js.impl.jac +5 -1
- jac_client/plugin/src/impl/package_installer.impl.jac +20 -20
- jac_client/plugin/src/impl/vite_bundler.impl.jac +191 -64
- jac_client/plugin/src/targets/desktop/sidecar/main.py +144 -0
- jac_client/plugin/src/targets/desktop_target.jac +37 -0
- jac_client/plugin/src/targets/impl/desktop_target.impl.jac +2347 -0
- jac_client/plugin/src/targets/impl/registry.impl.jac +64 -0
- jac_client/plugin/src/targets/impl/web_target.impl.jac +157 -0
- jac_client/plugin/src/targets/register.jac +21 -0
- jac_client/plugin/src/targets/registry.jac +87 -0
- jac_client/plugin/src/targets/web_target.jac +35 -0
- jac_client/plugin/src/vite_bundler.jac +6 -0
- jac_client/plugin/utils/__init__.jac +3 -0
- jac_client/plugin/utils/bun_installer.jac +16 -0
- jac_client/plugin/utils/impl/bun_installer.impl.jac +99 -0
- jac_client/templates/client.jacpack +72 -0
- jac_client/templates/fullstack.jacpack +61 -0
- jac_client/tests/conftest.py +103 -47
- jac_client/tests/fixtures/spawn_test/app.jac +49 -52
- jac_client/tests/fixtures/with-ts/app.jac +27 -27
- jac_client/tests/test_cli.py +182 -71
- jac_client/tests/test_e2e.py +232 -0
- jac_client/tests/test_helpers.py +58 -0
- jac_client/tests/test_it.py +91 -135
- jac_client/tests/test_it_desktop.py +891 -0
- {jac_client-0.2.8.dist-info → jac_client-0.2.11.dist-info}/METADATA +6 -6
- jac_client-0.2.11.dist-info/RECORD +113 -0
- {jac_client-0.2.8.dist-info → jac_client-0.2.11.dist-info}/WHEEL +1 -1
- jac_client/examples/all-in-one/app.jac +0 -573
- jac_client/examples/all-in-one/pages/BudgetPlanner.cl.jac +0 -70
- jac_client/examples/all-in-one/pages/FeaturesTest.cl.jac +0 -552
- jac_client/examples/asset-serving/css-with-image/src/app.jac +0 -88
- jac_client/examples/asset-serving/image-asset/src/app.jac +0 -55
- jac_client/examples/asset-serving/import-alias/src/app.jac +0 -111
- jac_client/examples/basic/src/app.jac +0 -21
- jac_client/examples/basic-auth/src/app.jac +0 -371
- jac_client/examples/basic-auth-with-router/src/app.jac +0 -464
- jac_client/examples/basic-full-stack/src/app.jac +0 -359
- jac_client/examples/css-styling/js-styling/src/app.jac +0 -84
- jac_client/examples/css-styling/material-ui/src/app.jac +0 -122
- jac_client/examples/css-styling/pure-css/src/app.jac +0 -64
- jac_client/examples/css-styling/sass-example/src/app.jac +0 -64
- jac_client/examples/css-styling/styled-components/src/app.jac +0 -71
- jac_client/examples/css-styling/tailwind-example/src/app.jac +0 -63
- jac_client/examples/full-stack-with-auth/src/app.jac +0 -722
- jac_client/examples/little-x/src/app.jac +0 -719
- jac_client/examples/nested-folders/nested-advance/src/app.jac +0 -35
- jac_client/examples/ts-support/src/app.jac +0 -35
- jac_client/examples/with-router/src/app.jac +0 -323
- jac_client/plugin/src/babel_processor.jac +0 -18
- jac_client/plugin/src/impl/babel_processor.impl.jac +0 -89
- jac_client-0.2.8.dist-info/RECORD +0 -97
- {jac_client-0.2.8.dist-info → jac_client-0.2.11.dist-info}/entry_points.txt +0 -0
- {jac_client-0.2.8.dist-info → jac_client-0.2.11.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fullstack",
|
|
3
|
+
"description": "Full-stack Jac app with authenticated todo list example",
|
|
4
|
+
"config": {
|
|
5
|
+
"project": {
|
|
6
|
+
"name": "{{name}}",
|
|
7
|
+
"version": "0.1.0",
|
|
8
|
+
"description": "A full-stack Jac application",
|
|
9
|
+
"entry-point": "main.jac"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {},
|
|
12
|
+
"dependencies.npm": {
|
|
13
|
+
"jac-client-node": "1.0.4"
|
|
14
|
+
},
|
|
15
|
+
"dependencies.npm.dev": {
|
|
16
|
+
"@jac-client/dev-deps": "1.0.0"
|
|
17
|
+
},
|
|
18
|
+
"dev-dependencies": {
|
|
19
|
+
"watchdog": ">=3.0.0"
|
|
20
|
+
},
|
|
21
|
+
"serve": {
|
|
22
|
+
"base_route_app": "app"
|
|
23
|
+
},
|
|
24
|
+
"plugins.client": {}
|
|
25
|
+
},
|
|
26
|
+
"files": {
|
|
27
|
+
"main.jac": "\"\"\"{{name}} - Entry Point.\n\nCombines server-side endpoints with client-side UI.\n\"\"\"\n\n# Import server-side data models and walkers\nsv {\n import from endpoints { Todo, AddTodo, ListTodos, ToggleTodo, DeleteTodo }\n}\n\n# Client-side UI\ncl {\n import from .frontend { app as ClientApp }\n\n def:pub app -> any {\n return\n <ClientApp />;\n }\n}\n",
|
|
28
|
+
"endpoints.sv.jac": "\"\"\"Todo App - Server-Side Data Layer.\n\nEach user gets their own todo list automatically!\nWhen authenticated, `root spawn` uses the user's personal root node.\n\"\"\"\n\n# ========================================\n# Data Models\n# ========================================\nnode Todo {\n has id: str,\n title: str,\n completed: bool = False;\n}\n\nglob todo_counter: int = 0;\n\n# ========================================\n# Walkers (API Operations)\n# ========================================\nwalker:priv AddTodo {\n \"\"\"Add a new todo item to the user's list.\"\"\"\n has title: str;\n\n can create with `root entry {\n global todo_counter;\n todo_counter += 1;\n new_id = \"todo_\" + str(todo_counter);\n new_todo = here ++> Todo(id=new_id, title=self.title, completed=False);\n if new_todo {\n report new_todo[0] ;\n }\n }\n}\n\nwalker:priv ListTodos {\n \"\"\"List all todo items for the current user.\"\"\"\n has todos: list = [];\n\n can collect with `root entry {\n visit [-->];\n }\n\n can gather with Todo entry {\n self.todos.append(\n {\"id\": here.id, \"title\": here.title, \"completed\": here.completed}\n );\n }\n\n can report_all with `root exit {\n report self.todos ;\n }\n}\n\nwalker:priv ToggleTodo {\n \"\"\"Toggle a todo's completed status.\"\"\"\n has todo_id: str;\n\n can search with `root entry {\n visit [-->];\n }\n\n can toggle with Todo entry {\n if here.id == self.todo_id {\n here.completed = not here.completed;\n report {\"id\": here.id, \"completed\": here.completed} ;\n }\n }\n}\n\nwalker:priv DeleteTodo {\n \"\"\"Delete a todo item.\"\"\"\n has todo_id: str;\n\n can search with `root entry {\n visit [-->];\n }\n\n can delete with Todo entry {\n if here.id == self.todo_id {\n del here ;\n report {\"deleted\": self.todo_id} ;\n }\n }\n}\n",
|
|
29
|
+
"frontend.cl.jac": "\"\"\"Todo App - Client-Side UI.\"\"\"\n\nimport from react { useEffect }\nimport from \"@jac/runtime\" { jacSignup, jacLogin, jacLogout, jacIsLoggedIn }\n\nimport from .components.TodoItem { TodoItem }\nimport from .components.AuthForm { AuthForm }\n\nsv import from endpoints { AddTodo, ListTodos, ToggleTodo, DeleteTodo }\n\ndef:pub app -> any {\n has isLoggedIn: bool = False,\n checkingAuth: bool = True,\n isSignup: bool = False,\n username: str = \"\",\n password: str = \"\",\n error: str = \"\",\n loading: bool = False,\n todos: list = [],\n newTodoText: str = \"\",\n todosLoading: bool = True;\n\n # Check login status on mount\n useEffect(lambda -> None { isLoggedIn = jacIsLoggedIn();checkingAuth = False;}, []);\n\n # Load todos when logged in\n useEffect(\n lambda -> None { if isLoggedIn {\n fetchTodos();\n }},\n [isLoggedIn]\n );\n\n # Fetch todos from server\n async def fetchTodos -> None {\n todosLoading = True;\n result = root spawn ListTodos();\n todos = result.reports[0] if result.reports else [];\n todosLoading = False;\n }\n\n # Add a new todo\n async def addTodo -> None {\n if not newTodoText.trim() {\n return;\n }\n response = root spawn AddTodo(title=newTodoText);\n newTodo = response.reports[0];\n todos = todos.concat(\n [\n {\n \"id\": newTodo.id,\n \"title\": newTodo.title,\n \"completed\": newTodo.completed\n }\n ]\n );\n newTodoText = \"\";\n }\n\n # Toggle todo completion\n async def toggleTodo(todoId: str) -> None {\n root spawn ToggleTodo(todo_id=todoId);\n todos = todos.map(\n lambda t: any -> any { if t.id == todoId {\n return {\"id\": t.id, \"title\": t.title, \"completed\": not t.completed};\n }return t; }\n );\n }\n\n # Delete a todo\n async def deleteTodo(todoId: str) -> None {\n root spawn DeleteTodo(todo_id=todoId);\n todos = todos.filter(lambda t: any -> bool { return t.id != todoId; });\n }\n\n # Handle login\n async def handleLogin -> None {\n error = \"\";\n if not username.trim() or not password {\n error = \"Please fill in all fields\";\n return;\n }\n loading = True;\n success = await jacLogin(username, password);\n loading = False;\n if success {\n isLoggedIn = True;\n username = \"\";\n password = \"\";\n } else {\n error = \"Invalid username or password\";\n }\n }\n\n # Handle signup\n async def handleSignup -> None {\n error = \"\";\n if not username.trim() or not password {\n error = \"Please fill in all fields\";\n return;\n }\n if password.length < 4 {\n error = \"Password must be at least 4 characters\";\n return;\n }\n loading = True;\n result = await jacSignup(username, password);\n loading = False;\n if result[\"success\"] {\n isLoggedIn = True;\n username = \"\";\n password = \"\";\n } else {\n error = result[\"error\"] if result[\"error\"] else \"Signup failed\";\n }\n }\n\n # Handle logout\n def handleLogout -> None {\n jacLogout();\n isLoggedIn = False;\n todos = [];\n username = \"\";\n password = \"\";\n error = \"\";\n }\n\n # Handle form submit\n async def handleSubmit(e: any) -> None {\n e.preventDefault();\n if isSignup {\n await handleSignup();\n } else {\n await handleLogin();\n }\n }\n\n # Handle enter key for todo input\n def handleTodoKeyPress(e: any) -> None {\n if e.key == \"Enter\" {\n addTodo();\n }\n }\n\n # Loading screen\n if checkingAuth {\n return\n <div\n style={{\n \"display\": \"flex\",\n \"justifyContent\": \"center\",\n \"alignItems\": \"center\",\n \"height\": \"100vh\",\n \"fontFamily\": \"system-ui, -apple-system, sans-serif\",\n \"color\": \"#666\"\n }}\n >\n Loading...\n </div>;\n }\n\n # Logged in - Show Todo List\n if isLoggedIn {\n return\n <div\n style={{\n \"maxWidth\": \"600px\",\n \"margin\": \"2rem auto\",\n \"padding\": \"2rem\",\n \"fontFamily\": \"system-ui, -apple-system, sans-serif\"\n }}\n >\n <div\n style={{\n \"display\": \"flex\",\n \"justifyContent\": \"space-between\",\n \"alignItems\": \"center\",\n \"marginBottom\": \"1.5rem\"\n }}\n >\n <h1 style={{\"margin\": \"0\", \"color\": \"#1a1a2e\"}}>\n My Todos\n </h1>\n <button\n onClick={handleLogout}\n style={{\n \"padding\": \"0.5rem 1rem\",\n \"fontSize\": \"0.9rem\",\n \"backgroundColor\": \"#f8f9fa\",\n \"color\": \"#666\",\n \"border\": \"1px solid #ddd\",\n \"borderRadius\": \"6px\",\n \"cursor\": \"pointer\"\n }}\n >\n Log Out\n </button>\n </div>\n <div\n style={{\n \"display\": \"flex\",\n \"gap\": \"0.5rem\",\n \"marginBottom\": \"1.5rem\"\n }}\n >\n <input\n type=\"text\"\n value={newTodoText}\n onChange={lambda e: any -> None { newTodoText = e.target.value;}}\n onKeyPress={handleTodoKeyPress}\n placeholder=\"What needs to be done?\"\n style={{\n \"flex\": \"1\",\n \"padding\": \"0.75rem 1rem\",\n \"fontSize\": \"1rem\",\n \"border\": \"2px solid #e0e0e0\",\n \"borderRadius\": \"8px\",\n \"outline\": \"none\"\n }}\n />\n <button\n onClick={lambda -> None { addTodo();}}\n style={{\n \"padding\": \"0.75rem 1.5rem\",\n \"fontSize\": \"1rem\",\n \"backgroundColor\": \"#4CAF50\",\n \"color\": \"white\",\n \"border\": \"none\",\n \"borderRadius\": \"8px\",\n \"cursor\": \"pointer\"\n }}\n >\n Add\n </button>\n </div>\n {(\n <div style={{\"color\": \"#666\", \"textAlign\": \"center\"}}>\n Loading todos...\n </div>\n )\n if todosLoading\n else None}\n {(\n <div>\n {(\n <p\n style={{\n \"color\": \"#888\",\n \"textAlign\": \"center\",\n \"padding\": \"2rem\"\n }}\n >\n No todos yet. Add one above!\n </p>\n )\n if todos.length == 0\n else None}{todos.map(\n lambda todo: any -> any { return\n <TodoItem\n key={todo.id}\n todo={todo}\n onToggle={toggleTodo}\n onDelete={deleteTodo}\n />; }\n )}\n </div>\n )\n if not todosLoading\n else None}\n <div\n style={{\n \"marginTop\": \"2rem\",\n \"paddingTop\": \"1rem\",\n \"borderTop\": \"1px solid #eee\",\n \"color\": \"#888\",\n \"fontSize\": \"0.9rem\"\n }}\n >\n {todos.filter(lambda t: any -> bool { return not t.completed; }).length} items left\n </div>\n </div>;\n }\n\n # Not logged in - Show Auth Form\n return\n <AuthForm\n isSignup={isSignup}\n username={username}\n password={password}\n error={error}\n loading={loading}\n onUsernameChange={lambda e: any -> None { username = e.target.value;}}\n onPasswordChange={lambda e: any -> None { password = e.target.value;}}\n onSubmit={handleSubmit}\n onToggleMode={lambda -> None { isSignup = not isSignup;error = \"\";}}\n />;\n}\n",
|
|
30
|
+
"components/AuthForm.cl.jac": "\"\"\"Authentication form component for login/signup.\"\"\"\n\ndef:pub AuthForm(\n isSignup: bool,\n username: str,\n password: str,\n error: str,\n loading: bool,\n onUsernameChange: any,\n onPasswordChange: any,\n onSubmit: any,\n onToggleMode: any\n) -> any {\n return\n <div\n style={{\n \"maxWidth\": \"400px\",\n \"margin\": \"4rem auto\",\n \"padding\": \"2rem\",\n \"fontFamily\": \"system-ui, -apple-system, sans-serif\",\n \"backgroundColor\": \"#fff\",\n \"borderRadius\": \"12px\",\n \"boxShadow\": \"0 4px 6px rgba(0,0,0,0.1)\"\n }}\n >\n <h1\n style={{\n \"marginBottom\": \"0.5rem\",\n \"color\": \"#1a1a2e\",\n \"textAlign\": \"center\"\n }}\n >\n Jac Todo App\n </h1>\n <p\n style={{\n \"marginBottom\": \"1.5rem\",\n \"color\": \"#666\",\n \"textAlign\": \"center\"\n }}\n >\n {(\"Create an account\" if isSignup else \"Sign in to your account\")}\n </p>\n {(\n <div\n style={{\n \"padding\": \"0.75rem\",\n \"marginBottom\": \"1rem\",\n \"backgroundColor\": \"#fee\",\n \"color\": \"#c00\",\n \"borderRadius\": \"6px\",\n \"fontSize\": \"0.9rem\"\n }}\n >\n {error}\n </div>\n )\n if error\n else None}\n <form onSubmit={onSubmit}>\n <input\n type=\"text\"\n value={username}\n onChange={onUsernameChange}\n placeholder=\"Username\"\n style={{\n \"width\": \"100%\",\n \"padding\": \"0.75rem 1rem\",\n \"marginBottom\": \"0.75rem\",\n \"fontSize\": \"1rem\",\n \"border\": \"2px solid #e0e0e0\",\n \"borderRadius\": \"8px\",\n \"outline\": \"none\",\n \"boxSizing\": \"border-box\"\n }}\n />\n <input\n type=\"password\"\n value={password}\n onChange={onPasswordChange}\n placeholder=\"Password\"\n style={{\n \"width\": \"100%\",\n \"padding\": \"0.75rem 1rem\",\n \"marginBottom\": \"1rem\",\n \"fontSize\": \"1rem\",\n \"border\": \"2px solid #e0e0e0\",\n \"borderRadius\": \"8px\",\n \"outline\": \"none\",\n \"boxSizing\": \"border-box\"\n }}\n />\n <button\n type=\"submit\"\n disabled={loading}\n style={{\n \"width\": \"100%\",\n \"padding\": \"0.75rem\",\n \"fontSize\": \"1rem\",\n \"backgroundColor\": (\"#999\" if loading else \"#4CAF50\"),\n \"color\": \"white\",\n \"border\": \"none\",\n \"borderRadius\": \"8px\",\n \"cursor\": (\"not-allowed\" if loading else \"pointer\"),\n \"marginBottom\": \"1rem\"\n }}\n >\n {(\n (\n \"Processing...\"\n if loading\n else (\"Sign Up\" if isSignup else \"Log In\")\n )\n )}\n </button>\n </form>\n <p style={{\"textAlign\": \"center\", \"color\": \"#666\", \"fontSize\": \"0.9rem\"}}>\n {(\n \"Already have an account? \"\n if isSignup\n else \"Don't have an account? \"\n )}\n <span\n onClick={onToggleMode}\n style={{\n \"color\": \"#4CAF50\",\n \"cursor\": \"pointer\",\n \"fontWeight\": \"bold\"\n }}\n >\n {(\"Log In\" if isSignup else \"Sign Up\")}\n </span>\n </p>\n </div>;\n}\n",
|
|
31
|
+
"components/TodoItem.cl.jac": "\"\"\"Todo item component for displaying a single todo.\"\"\"\n\ndef:pub TodoItem(todo: dict, onToggle: any, onDelete: any) -> any {\n return\n <div\n key={todo.id}\n style={{\n \"display\": \"flex\",\n \"alignItems\": \"center\",\n \"gap\": \"0.75rem\",\n \"padding\": \"1rem\",\n \"marginBottom\": \"0.5rem\",\n \"backgroundColor\": \"#f8f9fa\",\n \"borderRadius\": \"8px\",\n \"border\": \"1px solid #e9ecef\"\n }}\n >\n <input\n type=\"checkbox\"\n checked={todo.completed}\n onChange={lambda -> None { onToggle(todo.id);}}\n style={{\"width\": \"20px\", \"height\": \"20px\", \"cursor\": \"pointer\"}}\n />\n <span\n style={{\n \"flex\": \"1\",\n \"textDecoration\": (\"line-through\" if todo.completed else \"none\"),\n \"color\": (\"#888\" if todo.completed else \"#333\")\n }}\n >\n {todo.title}\n </span>\n <button\n onClick={lambda -> None { onDelete(todo.id);}}\n style={{\n \"padding\": \"0.5rem\",\n \"backgroundColor\": \"transparent\",\n \"color\": \"#dc3545\",\n \"border\": \"none\",\n \"cursor\": \"pointer\",\n \"fontSize\": \"1.2rem\"\n }}\n >\n x\n </button>\n </div>;\n}\n",
|
|
32
|
+
"components/Button.cl.jac": "\"\"\"Button component for the Jac client application.\"\"\"\n\ndef:pub Button(\n label: str, onClick: any, variant: str = \"primary\", disabled: bool = False\n) -> any {\n base_styles = {\n \"padding\": \"0.75rem 1.5rem\",\n \"fontSize\": \"1rem\",\n \"fontWeight\": \"600\",\n \"borderRadius\": \"0.5rem\",\n \"border\": \"none\",\n \"cursor\": \"not-allowed\" if disabled else \"pointer\",\n \"transition\": \"all 0.2s ease\"\n };\n\n variant_styles = {\n \"primary\": {\n \"backgroundColor\": \"#9ca3af\" if disabled else \"#3b82f6\",\n \"color\": \"#ffffff\"\n },\n \"secondary\": {\n \"backgroundColor\": \"#e5e7eb\" if disabled else \"#6b7280\",\n \"color\": \"#ffffff\"\n }\n };\n\n return\n <button\n style={{** base_styles, ** variant_styles[variant]}}\n onClick={onClick}\n disabled={disabled}\n >\n {label}\n </button>;\n}\n",
|
|
33
|
+
"README.md": "# {{name}}\n\nA full-stack Jac application with user authentication and todo list functionality.\n\n## Project Structure\n\n```\n{{name}}/\n\u251c\u2500\u2500 jac.toml # Project configuration\n\u251c\u2500\u2500 main.jac # Main entry point (combines server + client)\n\u251c\u2500\u2500 endpoints.sv.jac # Server-side data models and walkers\n\u251c\u2500\u2500 frontend.cl.jac # Client-side React UI\n\u251c\u2500\u2500 components/ # Reusable client components\n\u2502 \u251c\u2500\u2500 AuthForm.cl.jac # Login/signup form\n\u2502 \u251c\u2500\u2500 TodoItem.cl.jac # Individual todo display\n\u2502 \u2514\u2500\u2500 Button.cl.jac # Reusable button component\n\u2514\u2500\u2500 assets/ # Static assets (images, fonts, etc.)\n```\n\n## Getting Started\n\nStart the development server:\n\n```bash\njac start main.jac\n```\n\nThen open your browser to the URL shown in the terminal.\n\n## Features\n\n- **User Authentication**: Sign up and log in with username/password\n- **Personal Todo Lists**: Each user gets their own isolated todo list\n- **CRUD Operations**: Create, read, update (toggle), and delete todos\n- **Real-time UI**: Responsive React frontend with instant updates\n\n## Architecture\n\nThis template demonstrates the full-stack Jac architecture:\n\n- **Server (`.sv.jac`)**: Defines data models (`node Todo`) and walkers for API operations\n- **Client (`.cl.jac`)**: React components with hooks and state management\n- **Entry Point (`main.jac`)**: Combines server and client imports\n\n## Adding Dependencies\n\nAdd npm packages with the --cl flag:\n\n```bash\njac add --cl react-router-dom\n```\n"
|
|
34
|
+
},
|
|
35
|
+
"directories": [
|
|
36
|
+
".jac",
|
|
37
|
+
"assets"
|
|
38
|
+
],
|
|
39
|
+
"gitignore_entries": [
|
|
40
|
+
"# Ignore all build artifacts in .jac directory",
|
|
41
|
+
"*"
|
|
42
|
+
],
|
|
43
|
+
"root_gitignore_entries": [
|
|
44
|
+
"# Jac build artifacts",
|
|
45
|
+
".jac/",
|
|
46
|
+
"",
|
|
47
|
+
"# Dependencies",
|
|
48
|
+
"node_modules/",
|
|
49
|
+
"",
|
|
50
|
+
"# IDE",
|
|
51
|
+
".vscode/",
|
|
52
|
+
".idea/"
|
|
53
|
+
],
|
|
54
|
+
"jaclang": "0.9.8",
|
|
55
|
+
"plugins": [
|
|
56
|
+
{
|
|
57
|
+
"name": "jac-client",
|
|
58
|
+
"version": "0.2.8"
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
}
|
jac_client/tests/conftest.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"""Pytest configuration and shared fixtures for jac-client tests.
|
|
2
2
|
|
|
3
3
|
This module provides session-scoped fixtures to optimize test execution by:
|
|
4
|
-
1. Running
|
|
4
|
+
1. Running bun install once per session and caching node_modules
|
|
5
5
|
2. Providing shared Vite build infrastructure
|
|
6
|
-
3. Mocking
|
|
6
|
+
3. Mocking bun install for tests that only need jac.toml manipulation
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
from __future__ import annotations
|
|
@@ -15,13 +15,31 @@ import subprocess
|
|
|
15
15
|
import sys
|
|
16
16
|
import tempfile
|
|
17
17
|
from collections.abc import Generator
|
|
18
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
18
19
|
from pathlib import Path
|
|
19
20
|
from unittest.mock import patch
|
|
20
21
|
|
|
21
22
|
import pytest
|
|
22
23
|
|
|
24
|
+
from jaclang.pycore.program import JacProgram
|
|
23
25
|
from jaclang.pycore.runtime import JacRuntime as Jac
|
|
24
|
-
from jaclang.pycore.runtime import JacRuntimeImpl, plugin_manager
|
|
26
|
+
from jaclang.pycore.runtime import JacRuntimeImpl, JacRuntimeInterface, plugin_manager
|
|
27
|
+
|
|
28
|
+
# =============================================================================
|
|
29
|
+
# Console Output Normalization - Disable Rich styling during tests
|
|
30
|
+
# =============================================================================
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@pytest.fixture(autouse=True)
|
|
34
|
+
def disable_rich_console_formatting(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
35
|
+
"""Disable Rich console formatting for consistent test output.
|
|
36
|
+
|
|
37
|
+
Sets NO_COLOR and NO_EMOJI environment variables to ensure tests
|
|
38
|
+
get plain text output without ANSI codes or emoji prefixes.
|
|
39
|
+
"""
|
|
40
|
+
monkeypatch.setenv("NO_COLOR", "1")
|
|
41
|
+
monkeypatch.setenv("NO_EMOJI", "1")
|
|
42
|
+
|
|
25
43
|
|
|
26
44
|
# Store unregistered plugins globally for session-level management
|
|
27
45
|
_external_plugins: list = []
|
|
@@ -65,37 +83,59 @@ def _get_jac_command() -> list[str]:
|
|
|
65
83
|
return [sys.executable, "-m", "jaclang"]
|
|
66
84
|
|
|
67
85
|
|
|
68
|
-
def
|
|
69
|
-
"""Get environment dict with
|
|
86
|
+
def _get_env_with_bun() -> dict[str, str]:
|
|
87
|
+
"""Get environment dict with bun in PATH."""
|
|
70
88
|
env = os.environ.copy()
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
npm_dir = str(Path(npm_path).parent)
|
|
89
|
+
bun_path = shutil.which("bun")
|
|
90
|
+
if bun_path:
|
|
91
|
+
bun_dir = str(Path(bun_path).parent)
|
|
75
92
|
current_path = env.get("PATH", "")
|
|
76
|
-
if
|
|
77
|
-
env["PATH"] = f"{
|
|
93
|
+
if bun_dir not in current_path:
|
|
94
|
+
env["PATH"] = f"{bun_dir}:{current_path}"
|
|
78
95
|
return env
|
|
79
96
|
|
|
80
97
|
|
|
81
98
|
@pytest.fixture(autouse=True)
|
|
82
|
-
def reset_jac_machine() -> Generator[None, None, None]:
|
|
99
|
+
def reset_jac_machine(tmp_path: Path) -> Generator[None, None, None]:
|
|
83
100
|
"""Reset Jac machine before and after each test."""
|
|
84
|
-
|
|
101
|
+
# Close existing context if any
|
|
102
|
+
if Jac.exec_ctx is not None:
|
|
103
|
+
Jac.exec_ctx.mem.close()
|
|
104
|
+
|
|
105
|
+
# Remove user .jac modules from sys.modules so they get re-imported fresh
|
|
106
|
+
# Keep jaclang.* and __main__ to avoid breaking dataclass references
|
|
107
|
+
for mod in list(Jac.loaded_modules.values()):
|
|
108
|
+
if not mod.__name__.startswith("jaclang.") and mod.__name__ != "__main__":
|
|
109
|
+
sys.modules.pop(mod.__name__, None)
|
|
110
|
+
Jac.loaded_modules.clear()
|
|
111
|
+
|
|
112
|
+
# Set up fresh state
|
|
113
|
+
Jac.base_path_dir = str(tmp_path)
|
|
114
|
+
Jac.program = JacProgram()
|
|
115
|
+
Jac.pool = ThreadPoolExecutor()
|
|
116
|
+
Jac.exec_ctx = JacRuntimeInterface.create_j_context(user_root=None)
|
|
117
|
+
|
|
85
118
|
yield
|
|
86
|
-
Jac.reset_machine()
|
|
87
119
|
|
|
120
|
+
# Cleanup after test
|
|
121
|
+
if Jac.exec_ctx is not None:
|
|
122
|
+
Jac.exec_ctx.mem.close()
|
|
123
|
+
for mod in list(Jac.loaded_modules.values()):
|
|
124
|
+
if not mod.__name__.startswith("jaclang.") and mod.__name__ != "__main__":
|
|
125
|
+
sys.modules.pop(mod.__name__, None)
|
|
126
|
+
Jac.loaded_modules.clear()
|
|
88
127
|
|
|
89
|
-
|
|
90
|
-
|
|
128
|
+
|
|
129
|
+
# Session-scoped cache for bun installation
|
|
130
|
+
_bun_cache_dir: Path | None = None
|
|
91
131
|
|
|
92
132
|
|
|
93
133
|
def _get_minimal_jac_toml() -> str:
|
|
94
|
-
"""Get minimal jac.toml content for
|
|
134
|
+
"""Get minimal jac.toml content for bun cache setup."""
|
|
95
135
|
return """[project]
|
|
96
|
-
name = "
|
|
136
|
+
name = "bun-cache"
|
|
97
137
|
version = "0.0.1"
|
|
98
|
-
description = "Cached
|
|
138
|
+
description = "Cached bun modules"
|
|
99
139
|
entry-point = "app.jac"
|
|
100
140
|
|
|
101
141
|
[plugins.client.vite.build]
|
|
@@ -104,30 +144,30 @@ minify = false
|
|
|
104
144
|
|
|
105
145
|
|
|
106
146
|
@pytest.fixture(scope="session")
|
|
107
|
-
def
|
|
108
|
-
"""Session-scoped fixture that provides a directory with
|
|
147
|
+
def bun_cache_dir() -> Generator[Path, None, None]:
|
|
148
|
+
"""Session-scoped fixture that provides a directory with bun packages installed.
|
|
109
149
|
|
|
110
|
-
This runs
|
|
150
|
+
This runs bun install once per test session and provides the path to the
|
|
111
151
|
.jac/client/configs directory containing node_modules.
|
|
112
152
|
"""
|
|
113
|
-
global
|
|
153
|
+
global _bun_cache_dir
|
|
114
154
|
|
|
115
|
-
if
|
|
116
|
-
yield
|
|
155
|
+
if _bun_cache_dir is not None and _bun_cache_dir.exists():
|
|
156
|
+
yield _bun_cache_dir
|
|
117
157
|
return
|
|
118
158
|
|
|
119
159
|
# Create a persistent temp directory for the session
|
|
120
|
-
cache_dir = Path(tempfile.mkdtemp(prefix="
|
|
160
|
+
cache_dir = Path(tempfile.mkdtemp(prefix="jac_bun_cache_"))
|
|
121
161
|
|
|
122
162
|
# Create jac.toml
|
|
123
163
|
jac_toml = cache_dir / "jac.toml"
|
|
124
164
|
jac_toml.write_text(_get_minimal_jac_toml())
|
|
125
165
|
|
|
126
|
-
# Run jac add --
|
|
166
|
+
# Run jac add --npm to install packages (flag name unchanged for backward compatibility)
|
|
127
167
|
jac_cmd = _get_jac_command()
|
|
128
|
-
env =
|
|
168
|
+
env = _get_env_with_bun()
|
|
129
169
|
result = subprocess.run(
|
|
130
|
-
[*jac_cmd, "add", "--
|
|
170
|
+
[*jac_cmd, "add", "--npm"],
|
|
131
171
|
cwd=cache_dir,
|
|
132
172
|
capture_output=True,
|
|
133
173
|
text=True,
|
|
@@ -137,36 +177,44 @@ def npm_cache_dir() -> Generator[Path, None, None]:
|
|
|
137
177
|
if result.returncode != 0:
|
|
138
178
|
# Clean up on failure
|
|
139
179
|
shutil.rmtree(cache_dir, ignore_errors=True)
|
|
140
|
-
pytest.skip(f"Failed to set up
|
|
180
|
+
pytest.skip(f"Failed to set up bun cache: {result.stderr}")
|
|
141
181
|
|
|
142
|
-
|
|
182
|
+
_bun_cache_dir = cache_dir
|
|
143
183
|
yield cache_dir
|
|
144
184
|
|
|
145
185
|
# Cleanup after all tests complete
|
|
146
186
|
shutil.rmtree(cache_dir, ignore_errors=True)
|
|
147
187
|
|
|
148
188
|
|
|
189
|
+
# Backward compatibility alias
|
|
190
|
+
@pytest.fixture(scope="session")
|
|
191
|
+
def npm_cache_dir(bun_cache_dir: Path) -> Generator[Path, None, None]:
|
|
192
|
+
"""Backward compatibility alias for bun_cache_dir."""
|
|
193
|
+
yield bun_cache_dir
|
|
194
|
+
|
|
195
|
+
|
|
149
196
|
@pytest.fixture
|
|
150
|
-
def vite_project_dir(
|
|
197
|
+
def vite_project_dir(bun_cache_dir: Path, tmp_path: Path) -> Path:
|
|
151
198
|
"""Fixture that provides a project directory with pre-installed node_modules.
|
|
152
199
|
|
|
153
|
-
This copies node_modules from the session cache instead of running
|
|
200
|
+
This copies node_modules from the session cache instead of running bun install.
|
|
154
201
|
"""
|
|
155
202
|
# Create jac.toml in the temp directory
|
|
156
203
|
jac_toml = tmp_path / "jac.toml"
|
|
157
204
|
jac_toml.write_text(_get_minimal_jac_toml())
|
|
158
205
|
|
|
159
206
|
# Copy .jac/client/configs directory (contains package.json)
|
|
160
|
-
source_configs =
|
|
207
|
+
source_configs = bun_cache_dir / ".jac" / "client" / "configs"
|
|
161
208
|
dest_configs = tmp_path / ".jac" / "client" / "configs"
|
|
162
209
|
if source_configs.exists():
|
|
163
210
|
dest_configs.parent.mkdir(parents=True, exist_ok=True)
|
|
164
211
|
shutil.copytree(source_configs, dest_configs, symlinks=True)
|
|
165
212
|
|
|
166
|
-
# Copy node_modules from
|
|
167
|
-
source_node_modules =
|
|
168
|
-
dest_node_modules = tmp_path / "node_modules"
|
|
213
|
+
# Copy node_modules from .jac/client/ (bun installs there)
|
|
214
|
+
source_node_modules = bun_cache_dir / ".jac" / "client" / "node_modules"
|
|
215
|
+
dest_node_modules = tmp_path / ".jac" / "client" / "node_modules"
|
|
169
216
|
if source_node_modules.exists():
|
|
217
|
+
dest_node_modules.parent.mkdir(parents=True, exist_ok=True)
|
|
170
218
|
shutil.copytree(source_node_modules, dest_node_modules, symlinks=True)
|
|
171
219
|
|
|
172
220
|
# Create required directories
|
|
@@ -178,7 +226,7 @@ def vite_project_dir(npm_cache_dir: Path, tmp_path: Path) -> Path:
|
|
|
178
226
|
|
|
179
227
|
|
|
180
228
|
@pytest.fixture
|
|
181
|
-
def vite_project_with_antd(
|
|
229
|
+
def vite_project_with_antd(bun_cache_dir: Path, tmp_path: Path) -> Path:
|
|
182
230
|
"""Fixture that provides a project directory with antd pre-installed."""
|
|
183
231
|
# Create jac.toml with antd dependency
|
|
184
232
|
jac_toml_content = """[project]
|
|
@@ -197,23 +245,24 @@ antd = "^6.0.0"
|
|
|
197
245
|
jac_toml.write_text(jac_toml_content)
|
|
198
246
|
|
|
199
247
|
# Copy base .jac/client/configs first for faster install
|
|
200
|
-
source_configs =
|
|
248
|
+
source_configs = bun_cache_dir / ".jac" / "client" / "configs"
|
|
201
249
|
dest_configs = tmp_path / ".jac" / "client" / "configs"
|
|
202
250
|
if source_configs.exists():
|
|
203
251
|
dest_configs.parent.mkdir(parents=True, exist_ok=True)
|
|
204
252
|
shutil.copytree(source_configs, dest_configs, symlinks=True)
|
|
205
253
|
|
|
206
|
-
# Copy base node_modules for faster install (
|
|
207
|
-
source_node_modules =
|
|
208
|
-
dest_node_modules = tmp_path / "node_modules"
|
|
254
|
+
# Copy base node_modules for faster install (bun will add antd on top)
|
|
255
|
+
source_node_modules = bun_cache_dir / ".jac" / "client" / "node_modules"
|
|
256
|
+
dest_node_modules = tmp_path / ".jac" / "client" / "node_modules"
|
|
209
257
|
if source_node_modules.exists():
|
|
258
|
+
dest_node_modules.parent.mkdir(parents=True, exist_ok=True)
|
|
210
259
|
shutil.copytree(source_node_modules, dest_node_modules, symlinks=True)
|
|
211
260
|
|
|
212
261
|
# Install antd on top (uses cached node_modules as base)
|
|
213
262
|
jac_cmd = _get_jac_command()
|
|
214
|
-
env =
|
|
263
|
+
env = _get_env_with_bun()
|
|
215
264
|
result = subprocess.run(
|
|
216
|
-
[*jac_cmd, "add", "--
|
|
265
|
+
[*jac_cmd, "add", "--npm"],
|
|
217
266
|
cwd=tmp_path,
|
|
218
267
|
capture_output=True,
|
|
219
268
|
text=True,
|
|
@@ -231,10 +280,10 @@ antd = "^6.0.0"
|
|
|
231
280
|
|
|
232
281
|
|
|
233
282
|
@pytest.fixture
|
|
234
|
-
def
|
|
235
|
-
"""Fixture that mocks
|
|
283
|
+
def mock_bun_install():
|
|
284
|
+
"""Fixture that mocks bun install for tests that only test jac.toml manipulation.
|
|
236
285
|
|
|
237
|
-
Use this for CLI tests (add/remove commands) that don't need actual
|
|
286
|
+
Use this for CLI tests (add/remove commands) that don't need actual packages.
|
|
238
287
|
"""
|
|
239
288
|
with patch(
|
|
240
289
|
"jac_client.plugin.src.package_installer.PackageInstaller._regenerate_and_install"
|
|
@@ -242,6 +291,13 @@ def mock_npm_install():
|
|
|
242
291
|
yield mock
|
|
243
292
|
|
|
244
293
|
|
|
294
|
+
# Backward compatibility alias
|
|
295
|
+
@pytest.fixture
|
|
296
|
+
def mock_npm_install(mock_bun_install: Generator) -> Generator:
|
|
297
|
+
"""Backward compatibility alias for mock_bun_install."""
|
|
298
|
+
yield mock_bun_install
|
|
299
|
+
|
|
300
|
+
|
|
245
301
|
@pytest.fixture
|
|
246
302
|
def cli_test_dir(tmp_path: Path) -> Path:
|
|
247
303
|
"""Fixture that provides a minimal test directory for CLI tests."""
|
|
@@ -9,7 +9,7 @@ walker test_walker {
|
|
|
9
9
|
has message: str = "Hello from walker";
|
|
10
10
|
|
|
11
11
|
can execute with `root entry {
|
|
12
|
-
report {"result": self.message}
|
|
12
|
+
report {"result": self.message};
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
|
|
@@ -17,7 +17,7 @@ walker parameterized_walker {
|
|
|
17
17
|
has value: int;
|
|
18
18
|
|
|
19
19
|
can execute with `root entry {
|
|
20
|
-
report {"computed": self.value * 2}
|
|
20
|
+
report {"computed": self.value * 2};
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
|
|
@@ -27,15 +27,13 @@ walker positional_walker {
|
|
|
27
27
|
has metadata: dict = {};
|
|
28
28
|
|
|
29
29
|
can execute with `root entry {
|
|
30
|
-
report {"label": self.label, "count": self.count, "meta": self.metadata}
|
|
30
|
+
report {"label": self.label, "count": self.count, "meta": self.metadata};
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
# Client-side code testing both spawn orderings
|
|
35
|
-
cl import from react { useEffect }
|
|
36
|
-
|
|
37
35
|
cl {
|
|
38
|
-
def app()
|
|
36
|
+
def app() -> any {
|
|
39
37
|
has standardResult: any = None;
|
|
40
38
|
has standardComputed: any = None;
|
|
41
39
|
has reverseResult: any = None;
|
|
@@ -44,7 +42,7 @@ cl {
|
|
|
44
42
|
has positionalResult: any = None;
|
|
45
43
|
has spreadResult: any = None;
|
|
46
44
|
|
|
47
|
-
async
|
|
45
|
+
async can with entry {
|
|
48
46
|
# Test standard spawn order: node spawn walker()
|
|
49
47
|
data1 = root spawn test_walker();
|
|
50
48
|
standardResult = data1;
|
|
@@ -76,51 +74,50 @@ cl {
|
|
|
76
74
|
spreadResult = data7;
|
|
77
75
|
}
|
|
78
76
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
return <div>
|
|
82
|
-
<h1>
|
|
83
|
-
Spawn Operator Test
|
|
84
|
-
</h1>
|
|
85
|
-
<h2>
|
|
86
|
-
Standard Order (node spawn walker)
|
|
87
|
-
</h2>
|
|
88
|
-
<div>
|
|
89
|
-
Result: {JSON.stringify(standardResult)}
|
|
90
|
-
</div>
|
|
91
|
-
<div>
|
|
92
|
-
Computed: {JSON.stringify(standardComputed)}
|
|
93
|
-
</div>
|
|
94
|
-
<h2>
|
|
95
|
-
Reverse Order (walker spawn node)
|
|
96
|
-
</h2>
|
|
97
|
-
<div>
|
|
98
|
-
Result: {JSON.stringify(reverseResult)}
|
|
99
|
-
</div>
|
|
100
|
-
<h2>
|
|
101
|
-
UUID Spawn (uuid spawn walker)
|
|
102
|
-
</h2>
|
|
103
|
-
<div>
|
|
104
|
-
Result: {JSON.stringify(uuidResult)}
|
|
105
|
-
</div>
|
|
106
|
-
<h2>
|
|
107
|
-
Reverse UUID Spawn (walker spawn uuid)
|
|
108
|
-
</h2>
|
|
109
|
-
<div>
|
|
110
|
-
Result: {JSON.stringify(reverseUuidResult)}
|
|
111
|
-
</div>
|
|
112
|
-
<h2>
|
|
113
|
-
Positional Walker Arguments
|
|
114
|
-
</h2>
|
|
115
|
-
<div>
|
|
116
|
-
Result: {JSON.stringify(positionalResult)}
|
|
117
|
-
</div>
|
|
118
|
-
<h2>
|
|
119
|
-
Spread Walker Arguments
|
|
120
|
-
</h2>
|
|
77
|
+
return
|
|
121
78
|
<div>
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
79
|
+
<h1>
|
|
80
|
+
Spawn Operator Test
|
|
81
|
+
</h1>
|
|
82
|
+
<h2>
|
|
83
|
+
Standard Order (node spawn walker)
|
|
84
|
+
</h2>
|
|
85
|
+
<div>
|
|
86
|
+
Result: {JSON.stringify(standardResult)}
|
|
87
|
+
</div>
|
|
88
|
+
<div>
|
|
89
|
+
Computed: {JSON.stringify(standardComputed)}
|
|
90
|
+
</div>
|
|
91
|
+
<h2>
|
|
92
|
+
Reverse Order (walker spawn node)
|
|
93
|
+
</h2>
|
|
94
|
+
<div>
|
|
95
|
+
Result: {JSON.stringify(reverseResult)}
|
|
96
|
+
</div>
|
|
97
|
+
<h2>
|
|
98
|
+
UUID Spawn (uuid spawn walker)
|
|
99
|
+
</h2>
|
|
100
|
+
<div>
|
|
101
|
+
Result: {JSON.stringify(uuidResult)}
|
|
102
|
+
</div>
|
|
103
|
+
<h2>
|
|
104
|
+
Reverse UUID Spawn (walker spawn uuid)
|
|
105
|
+
</h2>
|
|
106
|
+
<div>
|
|
107
|
+
Result: {JSON.stringify(reverseUuidResult)}
|
|
108
|
+
</div>
|
|
109
|
+
<h2>
|
|
110
|
+
Positional Walker Arguments
|
|
111
|
+
</h2>
|
|
112
|
+
<div>
|
|
113
|
+
Result: {JSON.stringify(positionalResult)}
|
|
114
|
+
</div>
|
|
115
|
+
<h2>
|
|
116
|
+
Spread Walker Arguments
|
|
117
|
+
</h2>
|
|
118
|
+
<div>
|
|
119
|
+
Result: {JSON.stringify(spreadResult)}
|
|
120
|
+
</div>
|
|
121
|
+
</div>;
|
|
125
122
|
}
|
|
126
123
|
}
|
|
@@ -1,35 +1,35 @@
|
|
|
1
1
|
|
|
2
2
|
# Pages
|
|
3
|
-
cl import from react { useEffect }
|
|
4
3
|
cl import from ".components/Button.tsx" { Button }
|
|
5
4
|
|
|
6
5
|
cl {
|
|
7
|
-
def app()
|
|
6
|
+
def app() -> any {
|
|
8
7
|
has count: int = 0;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
<
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
8
|
+
|
|
9
|
+
can with count entry {
|
|
10
|
+
console.log("Count: ", count);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return
|
|
14
|
+
<div style={{padding: "2rem", fontFamily: "Arial, sans-serif"}}>
|
|
15
|
+
<h1>
|
|
16
|
+
Hello, World!
|
|
17
|
+
</h1>
|
|
18
|
+
<p>
|
|
19
|
+
Count: {count}
|
|
20
|
+
</p>
|
|
21
|
+
<div style={{display: "flex", gap: "1rem", marginTop: "1rem"}}>
|
|
22
|
+
<Button
|
|
23
|
+
label="Increment"
|
|
24
|
+
onClick={lambda -> None { count = count + 1;}}
|
|
25
|
+
variant="primary"
|
|
26
|
+
/>
|
|
27
|
+
<Button
|
|
28
|
+
label="Reset"
|
|
29
|
+
onClick={lambda -> None { count = 0;}}
|
|
30
|
+
variant="secondary"
|
|
31
|
+
/>
|
|
32
|
+
</div>
|
|
33
|
+
</div>;
|
|
34
34
|
}
|
|
35
35
|
}
|