create-miden-app 1.0.6 → 1.0.7
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/package.json +1 -1
- package/template/.claude/commands/review-security.md +67 -0
- package/template/.claude/settings.json +1 -7
- package/template/.claude/settings.local.json +24 -0
- package/template/.claude/skills/frontend-pitfalls/SKILL.md +28 -31
- package/template/.claude/skills/frontend-source-guide/SKILL.md +14 -14
- package/template/.claude/skills/miden-concepts/SKILL.md +4 -2
- package/template/.claude/skills/react-sdk-patterns/SKILL.md +294 -28
- package/template/.claude/skills/signer-integration/SKILL.md +22 -3
- package/template/.claude/skills/testing-patterns/SKILL.md +201 -40
- package/template/.claude/skills/vite-wasm-setup/SKILL.md +20 -14
- package/template/.claude/skills/web-client-usage/SKILL.md +454 -0
- package/template/.env.example +15 -2
- package/template/.mcp.json +9 -0
- package/template/CLAUDE.md +49 -16
- package/template/README.md +85 -19
- package/template/package.json +5 -4
- package/template/public/packages/counter_account.masp +0 -0
- package/template/public/packages/increment_note.masp +0 -0
- package/template/src/__tests__/fixtures/accounts.ts +17 -6
- package/template/src/__tests__/fixtures/index.ts +1 -0
- package/template/src/__tests__/mocks/miden-sdk-react.ts +18 -1
- package/template/src/__tests__/patterns/mutation-hook.test.tsx +2 -2
- package/template/src/__tests__/patterns/provider-setup.test.tsx +2 -0
- package/template/src/components/AppContent.tsx +33 -3
- package/template/{create-miden-app/template/src/components/Counter.tsx → src/components/ConfiguredCounter.tsx} +7 -4
- package/template/src/components/Counter.tsx +12 -41
- package/template/src/components/__tests__/AppContent.test.tsx +192 -4
- package/template/src/components/__tests__/ConfiguredCounter.test.tsx +116 -0
- package/template/src/components/__tests__/Counter.test.tsx +24 -94
- package/template/src/config.ts +26 -6
- package/template/src/hooks/__tests__/useIncrementCounter.test.tsx +257 -0
- package/template/src/hooks/useIncrementCounter.ts +109 -50
- package/template/src/providers.tsx +20 -24
- package/template/vite.config.ts +1 -1
- package/template/vitest.config.ts +1 -2
- package/template/yarn.lock +761 -688
- package/template/create-miden-app/template/.claude/hooks/typecheck.sh +0 -27
- package/template/create-miden-app/template/.claude/settings.json +0 -17
- package/template/create-miden-app/template/.claude/skills/frontend-pitfalls/SKILL.md +0 -189
- package/template/create-miden-app/template/.claude/skills/frontend-source-guide/SKILL.md +0 -163
- package/template/create-miden-app/template/.claude/skills/miden-concepts/SKILL.md +0 -108
- package/template/create-miden-app/template/.claude/skills/react-sdk-patterns/SKILL.md +0 -294
- package/template/create-miden-app/template/.claude/skills/signer-integration/SKILL.md +0 -158
- package/template/create-miden-app/template/.claude/skills/vite-wasm-setup/SKILL.md +0 -128
- package/template/create-miden-app/template/.env.example +0 -5
- package/template/create-miden-app/template/CLAUDE.md +0 -116
- package/template/create-miden-app/template/README.md +0 -61
- package/template/create-miden-app/template/eslint.config.js +0 -23
- package/template/create-miden-app/template/index.html +0 -13
- package/template/create-miden-app/template/package.json +0 -34
- package/template/create-miden-app/template/public/vite.svg +0 -1
- package/template/create-miden-app/template/src/App.tsx +0 -10
- package/template/create-miden-app/template/src/assets/miden.svg +0 -3
- package/template/create-miden-app/template/src/assets/react.svg +0 -1
- package/template/create-miden-app/template/src/components/AppContent.css +0 -45
- package/template/create-miden-app/template/src/components/AppContent.tsx +0 -50
- package/template/create-miden-app/template/src/components/Counter.css +0 -27
- package/template/create-miden-app/template/src/config.ts +0 -21
- package/template/create-miden-app/template/src/hooks/useIncrementCounter.ts +0 -136
- package/template/create-miden-app/template/src/index.css +0 -75
- package/template/create-miden-app/template/src/lib/miden.ts +0 -9
- package/template/create-miden-app/template/src/main.tsx +0 -10
- package/template/create-miden-app/template/src/providers.tsx +0 -31
- package/template/create-miden-app/template/src/vite-env.d.ts +0 -1
- package/template/create-miden-app/template/tsconfig.app.json +0 -32
- package/template/create-miden-app/template/tsconfig.json +0 -7
- package/template/create-miden-app/template/tsconfig.node.json +0 -24
- package/template/create-miden-app/template/vite.config.ts +0 -17
- package/template/create-miden-app/template/yarn.lock +0 -1697
|
@@ -2,19 +2,130 @@ import { render, screen } from "@testing-library/react";
|
|
|
2
2
|
import { vi, describe, it, expect, beforeEach } from "vitest";
|
|
3
3
|
|
|
4
4
|
vi.mock("@miden-sdk/react", () => import("@/__tests__/mocks/miden-sdk-react"));
|
|
5
|
-
vi.mock("@miden-sdk/miden-wallet-adapter", () => ({
|
|
6
|
-
|
|
5
|
+
vi.mock("@miden-sdk/miden-wallet-adapter-react", () => ({
|
|
6
|
+
useMidenFiWallet: vi.fn(() => ({
|
|
7
|
+
autoConnect: false,
|
|
8
|
+
wallets: [],
|
|
9
|
+
wallet: null,
|
|
10
|
+
address: null,
|
|
11
|
+
publicKey: null,
|
|
12
|
+
connected: false,
|
|
13
|
+
connecting: false,
|
|
14
|
+
disconnecting: false,
|
|
15
|
+
select: vi.fn(),
|
|
16
|
+
connect: vi.fn(async () => undefined),
|
|
17
|
+
disconnect: vi.fn(async () => undefined),
|
|
18
|
+
requestTransaction: vi.fn(async () => "0xtx"),
|
|
19
|
+
requestAssets: undefined,
|
|
20
|
+
requestPrivateNotes: undefined,
|
|
21
|
+
signBytes: undefined,
|
|
22
|
+
importPrivateNote: undefined,
|
|
23
|
+
requestConsumableNotes: undefined,
|
|
24
|
+
waitForTransaction: undefined,
|
|
25
|
+
requestSend: undefined,
|
|
26
|
+
requestConsume: undefined,
|
|
27
|
+
createAccount: undefined,
|
|
28
|
+
})),
|
|
29
|
+
}));
|
|
30
|
+
vi.mock("@miden-sdk/miden-wallet-adapter-base", () => ({
|
|
31
|
+
WalletReadyState: {
|
|
32
|
+
Installed: "Installed",
|
|
33
|
+
NotDetected: "NotDetected",
|
|
34
|
+
Loadable: "Loadable",
|
|
35
|
+
Unsupported: "Unsupported",
|
|
36
|
+
},
|
|
7
37
|
}));
|
|
8
38
|
vi.mock("@/components/Counter", () => ({
|
|
9
39
|
Counter: () => <div data-testid="counter">Counter Mock</div>,
|
|
10
40
|
}));
|
|
11
41
|
|
|
12
42
|
import { useMiden, useSyncState } from "@miden-sdk/react";
|
|
43
|
+
import { useMidenFiWallet } from "@miden-sdk/miden-wallet-adapter-react";
|
|
44
|
+
import userEvent from "@testing-library/user-event";
|
|
13
45
|
import { AppContent } from "../AppContent";
|
|
14
46
|
|
|
47
|
+
type WalletState = ReturnType<typeof useMidenFiWallet>;
|
|
48
|
+
type WalletInner = NonNullable<WalletState["wallet"]>;
|
|
49
|
+
|
|
50
|
+
function walletState(
|
|
51
|
+
overrides: Partial<{
|
|
52
|
+
readyState: "Installed" | "NotDetected" | "Loadable" | "Unsupported";
|
|
53
|
+
connected: boolean;
|
|
54
|
+
connecting: boolean;
|
|
55
|
+
address: string | null;
|
|
56
|
+
connect: () => Promise<void>;
|
|
57
|
+
disconnect: () => Promise<void>;
|
|
58
|
+
requestTransaction: WalletState["requestTransaction"];
|
|
59
|
+
}> = {},
|
|
60
|
+
): WalletState {
|
|
61
|
+
const {
|
|
62
|
+
readyState = "Installed",
|
|
63
|
+
connected = false,
|
|
64
|
+
connecting = false,
|
|
65
|
+
address = connected ? "mtst1arwk88k8smzcq5p30upr6eerw5npmnyz" : null,
|
|
66
|
+
connect = vi.fn(async () => undefined),
|
|
67
|
+
disconnect = vi.fn(async () => undefined),
|
|
68
|
+
requestTransaction = vi.fn(async () => "0xtx"),
|
|
69
|
+
} = overrides;
|
|
70
|
+
// Build a shape that satisfies WalletContextState; the inner `Wallet`
|
|
71
|
+
// (`{ adapter, readyState }`) shape requires an Adapter, which we stub
|
|
72
|
+
// with a structural cast since the component only reads `readyState`.
|
|
73
|
+
const innerWallet = {
|
|
74
|
+
adapter: {} as WalletInner["adapter"],
|
|
75
|
+
readyState,
|
|
76
|
+
} as WalletInner;
|
|
77
|
+
return {
|
|
78
|
+
autoConnect: false,
|
|
79
|
+
wallets: [innerWallet],
|
|
80
|
+
wallet: innerWallet,
|
|
81
|
+
address,
|
|
82
|
+
publicKey: null,
|
|
83
|
+
connected,
|
|
84
|
+
connecting,
|
|
85
|
+
disconnecting: false,
|
|
86
|
+
select: vi.fn(),
|
|
87
|
+
connect,
|
|
88
|
+
disconnect,
|
|
89
|
+
requestTransaction,
|
|
90
|
+
requestAssets: undefined,
|
|
91
|
+
requestPrivateNotes: undefined,
|
|
92
|
+
signBytes: undefined,
|
|
93
|
+
importPrivateNote: undefined,
|
|
94
|
+
requestConsumableNotes: undefined,
|
|
95
|
+
waitForTransaction: undefined,
|
|
96
|
+
requestSend: undefined,
|
|
97
|
+
requestConsume: undefined,
|
|
98
|
+
createAccount: undefined,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const midenReady = {
|
|
103
|
+
client: null,
|
|
104
|
+
isReady: true,
|
|
105
|
+
isInitializing: false,
|
|
106
|
+
error: null,
|
|
107
|
+
sync: vi.fn(),
|
|
108
|
+
runExclusive: vi.fn(),
|
|
109
|
+
prover: null,
|
|
110
|
+
signerAccountId: null,
|
|
111
|
+
signerConnected: null,
|
|
112
|
+
};
|
|
113
|
+
|
|
15
114
|
describe("AppContent", () => {
|
|
16
115
|
beforeEach(() => {
|
|
17
|
-
vi.
|
|
116
|
+
vi.resetAllMocks();
|
|
117
|
+
// Restore default ready state after any test that overrides useMiden
|
|
118
|
+
vi.mocked(useMiden).mockReturnValue(midenReady);
|
|
119
|
+
vi.mocked(useSyncState).mockReturnValue({
|
|
120
|
+
syncHeight: 12345,
|
|
121
|
+
isSyncing: false,
|
|
122
|
+
lastSyncTime: Date.now(),
|
|
123
|
+
error: null,
|
|
124
|
+
sync: vi.fn(),
|
|
125
|
+
});
|
|
126
|
+
vi.mocked(useMidenFiWallet).mockReturnValue(
|
|
127
|
+
walletState({ readyState: "NotDetected" }),
|
|
128
|
+
);
|
|
18
129
|
});
|
|
19
130
|
|
|
20
131
|
it("renders main content when Miden is ready", () => {
|
|
@@ -24,7 +135,6 @@ describe("AppContent", () => {
|
|
|
24
135
|
expect(screen.getByAltText("Vite logo")).toBeInTheDocument();
|
|
25
136
|
expect(screen.getByAltText("React logo")).toBeInTheDocument();
|
|
26
137
|
expect(screen.getByAltText("Miden logo")).toBeInTheDocument();
|
|
27
|
-
expect(screen.getByText("Connect Wallet")).toBeInTheDocument();
|
|
28
138
|
expect(screen.getByTestId("counter")).toBeInTheDocument();
|
|
29
139
|
});
|
|
30
140
|
|
|
@@ -56,6 +166,7 @@ describe("AppContent", () => {
|
|
|
56
166
|
runExclusive: vi.fn(),
|
|
57
167
|
prover: null,
|
|
58
168
|
signerAccountId: null,
|
|
169
|
+
signerConnected: null,
|
|
59
170
|
});
|
|
60
171
|
|
|
61
172
|
render(<AppContent />);
|
|
@@ -65,6 +176,82 @@ describe("AppContent", () => {
|
|
|
65
176
|
expect(screen.queryByText("Vite + React + Miden")).not.toBeInTheDocument();
|
|
66
177
|
});
|
|
67
178
|
|
|
179
|
+
it("shows disabled install-wallet button when extension is not detected", () => {
|
|
180
|
+
render(<AppContent />);
|
|
181
|
+
const button = screen.getByRole("button", { name: "Install MidenFi Wallet" });
|
|
182
|
+
expect(button).toBeDisabled();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("shows connect button when wallet is installed and disconnected", () => {
|
|
186
|
+
vi.mocked(useMidenFiWallet).mockReturnValue(
|
|
187
|
+
walletState({ readyState: "Installed", connected: false }),
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
render(<AppContent />);
|
|
191
|
+
expect(
|
|
192
|
+
screen.getByRole("button", { name: "Connect Wallet" }),
|
|
193
|
+
).toBeEnabled();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("shows disconnect button when wallet is connected", () => {
|
|
197
|
+
vi.mocked(useMidenFiWallet).mockReturnValue(
|
|
198
|
+
walletState({ readyState: "Installed", connected: true }),
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
render(<AppContent />);
|
|
202
|
+
expect(
|
|
203
|
+
screen.getByRole("button", { name: "Disconnect Wallet" }),
|
|
204
|
+
).toBeInTheDocument();
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it("shows connecting state while the wallet request is in flight", () => {
|
|
208
|
+
vi.mocked(useMidenFiWallet).mockReturnValue(
|
|
209
|
+
walletState({
|
|
210
|
+
readyState: "Installed",
|
|
211
|
+
connected: false,
|
|
212
|
+
connecting: true,
|
|
213
|
+
}),
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
render(<AppContent />);
|
|
217
|
+
const button = screen.getByRole("button", { name: /Connecting/ });
|
|
218
|
+
expect(button).toBeDisabled();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it("calls connect on wallet button click", async () => {
|
|
222
|
+
const mockConnect = vi.fn(async () => undefined);
|
|
223
|
+
vi.mocked(useMidenFiWallet).mockReturnValue(
|
|
224
|
+
walletState({
|
|
225
|
+
readyState: "Installed",
|
|
226
|
+
connected: false,
|
|
227
|
+
connect: mockConnect,
|
|
228
|
+
}),
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
render(<AppContent />);
|
|
232
|
+
const user = userEvent.setup();
|
|
233
|
+
await user.click(screen.getByRole("button", { name: "Connect Wallet" }));
|
|
234
|
+
expect(mockConnect).toHaveBeenCalledOnce();
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("calls disconnect on wallet button click", async () => {
|
|
238
|
+
const mockDisconnect = vi.fn(async () => undefined);
|
|
239
|
+
vi.mocked(useMidenFiWallet).mockReturnValue(
|
|
240
|
+
walletState({
|
|
241
|
+
readyState: "Installed",
|
|
242
|
+
connected: true,
|
|
243
|
+
disconnect: mockDisconnect,
|
|
244
|
+
}),
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
render(<AppContent />);
|
|
248
|
+
const user = userEvent.setup();
|
|
249
|
+
await user.click(
|
|
250
|
+
screen.getByRole("button", { name: "Disconnect Wallet" }),
|
|
251
|
+
);
|
|
252
|
+
expect(mockDisconnect).toHaveBeenCalledOnce();
|
|
253
|
+
});
|
|
254
|
+
|
|
68
255
|
it("shows error message on initialization failure", () => {
|
|
69
256
|
vi.mocked(useMiden).mockReturnValue({
|
|
70
257
|
client: null,
|
|
@@ -75,6 +262,7 @@ describe("AppContent", () => {
|
|
|
75
262
|
runExclusive: vi.fn(),
|
|
76
263
|
prover: null,
|
|
77
264
|
signerAccountId: null,
|
|
265
|
+
signerConnected: null,
|
|
78
266
|
});
|
|
79
267
|
|
|
80
268
|
render(<AppContent />);
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import userEvent from "@testing-library/user-event";
|
|
3
|
+
import { vi, describe, it, expect, beforeEach } from "vitest";
|
|
4
|
+
|
|
5
|
+
vi.mock("@/hooks/useIncrementCounter", () => ({
|
|
6
|
+
useIncrementCounter: vi.fn(),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
import { useIncrementCounter } from "@/hooks/useIncrementCounter";
|
|
10
|
+
import { ConfiguredCounter } from "../ConfiguredCounter";
|
|
11
|
+
|
|
12
|
+
const FIXTURE_ADDRESS = "0xdeadbeef00000001";
|
|
13
|
+
|
|
14
|
+
const defaultHookReturn = {
|
|
15
|
+
increment: vi.fn(),
|
|
16
|
+
count: 42,
|
|
17
|
+
isSubmitting: false,
|
|
18
|
+
isWaiting: false,
|
|
19
|
+
error: null,
|
|
20
|
+
walletConnected: true,
|
|
21
|
+
explorerUrl: `https://testnet.midenscan.com/account/${FIXTURE_ADDRESS}`,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
describe("ConfiguredCounter", () => {
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
vi.clearAllMocks();
|
|
27
|
+
vi.mocked(useIncrementCounter).mockReturnValue(defaultHookReturn);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("displays the current count on the button", () => {
|
|
31
|
+
render(<ConfiguredCounter counterAddress={FIXTURE_ADDRESS} />);
|
|
32
|
+
expect(
|
|
33
|
+
screen.getByRole("button", { name: "count is 42" }),
|
|
34
|
+
).toBeInTheDocument();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("calls increment on button click", async () => {
|
|
38
|
+
const mockIncrement = vi.fn();
|
|
39
|
+
vi.mocked(useIncrementCounter).mockReturnValue({
|
|
40
|
+
...defaultHookReturn,
|
|
41
|
+
increment: mockIncrement,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
render(<ConfiguredCounter counterAddress={FIXTURE_ADDRESS} />);
|
|
45
|
+
const user = userEvent.setup();
|
|
46
|
+
await user.click(screen.getByRole("button", { name: "count is 42" }));
|
|
47
|
+
expect(mockIncrement).toHaveBeenCalledOnce();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("shows submitting state", () => {
|
|
51
|
+
vi.mocked(useIncrementCounter).mockReturnValue({
|
|
52
|
+
...defaultHookReturn,
|
|
53
|
+
isSubmitting: true,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
render(<ConfiguredCounter counterAddress={FIXTURE_ADDRESS} />);
|
|
57
|
+
const button = screen.getByRole("button", { name: "Submitting..." });
|
|
58
|
+
expect(button).toBeDisabled();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("shows waiting for network state", () => {
|
|
62
|
+
vi.mocked(useIncrementCounter).mockReturnValue({
|
|
63
|
+
...defaultHookReturn,
|
|
64
|
+
isWaiting: true,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
render(<ConfiguredCounter counterAddress={FIXTURE_ADDRESS} />);
|
|
68
|
+
const button = screen.getByRole("button", {
|
|
69
|
+
name: "Waiting for network...",
|
|
70
|
+
});
|
|
71
|
+
expect(button).toBeDisabled();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("disables button when wallet not connected", () => {
|
|
75
|
+
vi.mocked(useIncrementCounter).mockReturnValue({
|
|
76
|
+
...defaultHookReturn,
|
|
77
|
+
walletConnected: false,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
render(<ConfiguredCounter counterAddress={FIXTURE_ADDRESS} />);
|
|
81
|
+
expect(screen.getByRole("button")).toBeDisabled();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("disables button when count is loading (null)", () => {
|
|
85
|
+
vi.mocked(useIncrementCounter).mockReturnValue({
|
|
86
|
+
...defaultHookReturn,
|
|
87
|
+
count: null,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
render(<ConfiguredCounter counterAddress={FIXTURE_ADDRESS} />);
|
|
91
|
+
const button = screen.getByRole("button", { name: "count is ..." });
|
|
92
|
+
expect(button).toBeDisabled();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("displays error message", () => {
|
|
96
|
+
vi.mocked(useIncrementCounter).mockReturnValue({
|
|
97
|
+
...defaultHookReturn,
|
|
98
|
+
error: "Transaction failed: insufficient funds",
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
render(<ConfiguredCounter counterAddress={FIXTURE_ADDRESS} />);
|
|
102
|
+
expect(
|
|
103
|
+
screen.getByText("Transaction failed: insufficient funds"),
|
|
104
|
+
).toBeInTheDocument();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("links to explorer with counter address", () => {
|
|
108
|
+
render(<ConfiguredCounter counterAddress={FIXTURE_ADDRESS} />);
|
|
109
|
+
const link = screen.getByRole("link");
|
|
110
|
+
expect(link).toHaveAttribute(
|
|
111
|
+
"href",
|
|
112
|
+
`https://testnet.midenscan.com/account/${FIXTURE_ADDRESS}`,
|
|
113
|
+
);
|
|
114
|
+
expect(link).toHaveAttribute("target", "_blank");
|
|
115
|
+
});
|
|
116
|
+
});
|
|
@@ -1,114 +1,44 @@
|
|
|
1
1
|
import { render, screen } from "@testing-library/react";
|
|
2
|
-
import userEvent from "@testing-library/user-event";
|
|
3
2
|
import { vi, describe, it, expect, beforeEach } from "vitest";
|
|
4
3
|
|
|
5
|
-
vi.mock("@/
|
|
6
|
-
|
|
4
|
+
vi.mock("@/components/ConfiguredCounter", () => ({
|
|
5
|
+
ConfiguredCounter: ({ counterAddress }: { counterAddress: string }) => (
|
|
6
|
+
<div data-testid="configured-counter">{counterAddress}</div>
|
|
7
|
+
),
|
|
7
8
|
}));
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
vi.mock("@/config", async () => {
|
|
11
|
+
const actual = await vi.importActual<typeof import("@/config")>("@/config");
|
|
12
|
+
return { ...actual };
|
|
13
|
+
});
|
|
11
14
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
count: 42,
|
|
15
|
-
isSubmitting: false,
|
|
16
|
-
isWaiting: false,
|
|
17
|
-
error: null,
|
|
18
|
-
walletConnected: true,
|
|
19
|
-
explorerUrl: "https://testnet.midenscan.com/account/mtst1test",
|
|
20
|
-
};
|
|
15
|
+
import { Counter } from "../Counter";
|
|
16
|
+
import * as config from "@/config";
|
|
21
17
|
|
|
22
|
-
describe("Counter", () => {
|
|
18
|
+
describe("Counter gate", () => {
|
|
23
19
|
beforeEach(() => {
|
|
24
20
|
vi.clearAllMocks();
|
|
25
|
-
vi.mocked(useIncrementCounter).mockReturnValue(defaultHookReturn);
|
|
26
21
|
});
|
|
27
22
|
|
|
28
|
-
it("
|
|
23
|
+
it("renders ConfiguredCounter when COUNTER_ADDRESS is set", () => {
|
|
29
24
|
render(<Counter />);
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
).
|
|
25
|
+
const configured = screen.getByTestId("configured-counter");
|
|
26
|
+
expect(configured).toBeInTheDocument();
|
|
27
|
+
expect(configured).toHaveTextContent(config.COUNTER_ADDRESS!);
|
|
33
28
|
});
|
|
34
29
|
|
|
35
|
-
it("
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
render(<Counter />);
|
|
43
|
-
const user = userEvent.setup();
|
|
44
|
-
await user.click(screen.getByRole("button", { name: "count is 42" }));
|
|
45
|
-
expect(mockIncrement).toHaveBeenCalledOnce();
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it("shows submitting state", () => {
|
|
49
|
-
vi.mocked(useIncrementCounter).mockReturnValue({
|
|
50
|
-
...defaultHookReturn,
|
|
51
|
-
isSubmitting: true,
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
render(<Counter />);
|
|
55
|
-
const button = screen.getByRole("button", { name: "Submitting..." });
|
|
56
|
-
expect(button).toBeDisabled();
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it("shows waiting for network state", () => {
|
|
60
|
-
vi.mocked(useIncrementCounter).mockReturnValue({
|
|
61
|
-
...defaultHookReturn,
|
|
62
|
-
isWaiting: true,
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
render(<Counter />);
|
|
66
|
-
const button = screen.getByRole("button", {
|
|
67
|
-
name: "Waiting for network...",
|
|
68
|
-
});
|
|
69
|
-
expect(button).toBeDisabled();
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it("disables button when wallet not connected", () => {
|
|
73
|
-
vi.mocked(useIncrementCounter).mockReturnValue({
|
|
74
|
-
...defaultHookReturn,
|
|
75
|
-
walletConnected: false,
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
render(<Counter />);
|
|
79
|
-
expect(screen.getByRole("button")).toBeDisabled();
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it("disables button when count is loading (null)", () => {
|
|
83
|
-
vi.mocked(useIncrementCounter).mockReturnValue({
|
|
84
|
-
...defaultHookReturn,
|
|
85
|
-
count: null,
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
render(<Counter />);
|
|
89
|
-
const button = screen.getByRole("button", { name: "count is ..." });
|
|
90
|
-
expect(button).toBeDisabled();
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it("displays error message", () => {
|
|
94
|
-
vi.mocked(useIncrementCounter).mockReturnValue({
|
|
95
|
-
...defaultHookReturn,
|
|
96
|
-
error: "Transaction failed: insufficient funds",
|
|
97
|
-
});
|
|
30
|
+
it("shows not-configured message when COUNTER_ADDRESS is null", () => {
|
|
31
|
+
// The env-wired resolver in `config.ts` returns `null` when
|
|
32
|
+
// `VITE_MIDEN_COUNTER_ADDRESS=""` (explicit empty string). Simulate that
|
|
33
|
+
// by overriding the module's exported value directly.
|
|
34
|
+
vi.spyOn(config, "COUNTER_ADDRESS", "get").mockReturnValue(null);
|
|
98
35
|
|
|
99
36
|
render(<Counter />);
|
|
100
37
|
expect(
|
|
101
|
-
screen.getByText(
|
|
38
|
+
screen.getByText(/counter address not configured/i),
|
|
102
39
|
).toBeInTheDocument();
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
render(<Counter />);
|
|
107
|
-
const link = screen.getByRole("link");
|
|
108
|
-
expect(link).toHaveAttribute(
|
|
109
|
-
"href",
|
|
110
|
-
"https://testnet.midenscan.com/account/mtst1test",
|
|
111
|
-
);
|
|
112
|
-
expect(link).toHaveAttribute("target", "_blank");
|
|
40
|
+
expect(
|
|
41
|
+
screen.queryByTestId("configured-counter"),
|
|
42
|
+
).not.toBeInTheDocument();
|
|
113
43
|
});
|
|
114
44
|
});
|
package/template/src/config.ts
CHANGED
|
@@ -1,15 +1,35 @@
|
|
|
1
|
-
// Network counter account deployed on Miden testnet
|
|
2
|
-
|
|
1
|
+
// Network counter account deployed on Miden testnet.
|
|
2
|
+
//
|
|
3
|
+
// Resolution rules for `COUNTER_ADDRESS`:
|
|
4
|
+
// - `VITE_MIDEN_COUNTER_ADDRESS` unset (or omitted) → use the live default
|
|
5
|
+
// deployment (the testnet counter the template ships with).
|
|
6
|
+
// - `VITE_MIDEN_COUNTER_ADDRESS=""` (explicit empty string) → unconfigured,
|
|
7
|
+
// `<Counter>` renders the "address not configured" card.
|
|
8
|
+
// - Any other string → that string is used verbatim (e.g. your own deploy).
|
|
9
|
+
const DEFAULT_COUNTER_ADDRESS = "mtst1aqmx7qv6h3y92sqsmunh8uht4ujmfy4j";
|
|
10
|
+
const configuredCounterAddress: string | undefined =
|
|
11
|
+
import.meta.env.VITE_MIDEN_COUNTER_ADDRESS;
|
|
12
|
+
|
|
13
|
+
export const COUNTER_ADDRESS: string | null =
|
|
14
|
+
configuredCounterAddress === ""
|
|
15
|
+
? null
|
|
16
|
+
: (configuredCounterAddress ?? DEFAULT_COUNTER_ADDRESS);
|
|
3
17
|
|
|
4
18
|
// StorageMap slot name for the counter
|
|
5
19
|
export const COUNTER_SLOT_NAME =
|
|
6
|
-
"
|
|
20
|
+
"miden_counter_account::counter_contract::count_map";
|
|
7
21
|
|
|
8
22
|
// Block explorer base URL
|
|
9
23
|
export const EXPLORER_BASE_URL = "https://testnet.midenscan.com";
|
|
10
24
|
|
|
11
|
-
//
|
|
12
|
-
|
|
25
|
+
// Poll interval (ms) while waiting for the network operator to consume an
|
|
26
|
+
// increment note and update the counter's on-chain state.
|
|
27
|
+
export const NETWORK_POLL_INTERVAL_MS = 2_500;
|
|
28
|
+
|
|
29
|
+
// Hard cap (ms) on how long to poll for the post-increment state change before
|
|
30
|
+
// giving up and showing whatever value the counter currently has. Covers
|
|
31
|
+
// ~3 block cycles at testnet's ~3s block time with margin.
|
|
32
|
+
export const NETWORK_POLL_TIMEOUT_MS = 30_000;
|
|
13
33
|
|
|
14
34
|
// Application display name (used by wallet adapter)
|
|
15
35
|
export const APP_NAME = "Miden Template";
|
|
@@ -18,4 +38,4 @@ export const APP_NAME = "Miden Template";
|
|
|
18
38
|
export const MIDEN_RPC_URL =
|
|
19
39
|
import.meta.env.VITE_MIDEN_RPC_URL ?? "testnet";
|
|
20
40
|
export const MIDEN_PROVER =
|
|
21
|
-
(import.meta.env.VITE_MIDEN_PROVER as "testnet" | "local") ?? "testnet";
|
|
41
|
+
(import.meta.env.VITE_MIDEN_PROVER as "devnet" | "testnet" | "local") ?? "testnet";
|