-
Notifications
You must be signed in to change notification settings - Fork 132
Add useFlowSchedule hook #2653
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Add useFlowSchedule hook #2653
Changes from 7 commits
4afcbd0
f1e376b
808282f
0374705
77295d3
d62421d
f431cab
cbc1d78
6950c0b
f0517cb
70440a3
c6bbe8a
3862072
c2023b3
c20706e
b296b19
0fedcd8
27b072e
dfedfc2
96bf4ba
db9c4bc
567e80a
8a761cf
66f39dd
7fee026
ee91c3f
b22ea28
ffd246a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@onflow/react-sdk": minor | ||
--- | ||
|
||
Added useFlowSchedule hook | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,6 +11,8 @@ export const CONTRACT_ADDRESSES = { | |
FlowEVMBridgeUtils: "0xdfc20aee650fcbdf", | ||
FlowEVMBridgeConfig: "0xdfc20aee650fcbdf", | ||
FungibleTokenMetadataViews: "0x9a0766d93b6608b7", | ||
FlowTransactionScheduler: "0x8c5303eaa26202d6", | ||
FlowTransactionSchedulerUtils: "0x8c5303eaa26202d6", | ||
}, | ||
mainnet: { | ||
EVM: "0xe467b9dd11fa00df", | ||
|
@@ -24,6 +26,9 @@ export const CONTRACT_ADDRESSES = { | |
FlowEVMBridgeUtils: "0x1e4aa0b87d10b141", | ||
FlowEVMBridgeConfig: "0x1e4aa0b87d10b141", | ||
FungibleTokenMetadataViews: "0xf233dcee88fe0abe", | ||
// TODO: Update with mainnet addresses | ||
FlowTransactionScheduler: "0x0000000000000000", | ||
FlowTransactionSchedulerUtils: "0x0000000000000000", | ||
Comment on lines
+30
to
+31
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The placeholder addresses for mainnet (0x0000000000000000) could cause runtime errors if used before being updated. Consider adding runtime validation to throw a descriptive error when these placeholder addresses are detected, helping developers identify the issue quickly. Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||
}, | ||
local: { | ||
EVM: "0xf8d6e0586b0a20c7", | ||
|
@@ -37,6 +42,8 @@ export const CONTRACT_ADDRESSES = { | |
FlowEVMBridgeUtils: "0xf8d6e0586b0a20c7", | ||
FlowEVMBridgeConfig: "0xf8d6e0586b0a20c7", | ||
FungibleTokenMetadataViews: "0xee82856bf20e2aa6", | ||
FlowTransactionScheduler: "0xf8d6e0586b0a20c7", | ||
FlowTransactionSchedulerUtils: "0xf8d6e0586b0a20c7", | ||
}, | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,310 @@ | ||
import * as fcl from "@onflow/fcl" | ||
import {renderHook} from "@testing-library/react" | ||
import {createMockFclInstance, MockFclInstance} from "../__mocks__/flow-client" | ||
import {FlowProvider} from "../provider" | ||
import {useFlowChainId} from "./useFlowChainId" | ||
import { | ||
TransactionInfoWithHandler, | ||
TransactionPriority, | ||
TransactionStatus, | ||
useFlowSchedule, | ||
} from "./useFlowSchedule" | ||
|
||
jest.mock("@onflow/fcl", () => require("../__mocks__/fcl").default) | ||
jest.mock("./useFlowChainId", () => ({ | ||
useFlowChainId: jest.fn(), | ||
})) | ||
|
||
describe("useFlowSchedule", () => { | ||
let mockFcl: MockFclInstance | ||
|
||
beforeEach(() => { | ||
jest.clearAllMocks() | ||
jest.mocked(useFlowChainId).mockReturnValue({ | ||
data: "testnet", | ||
isLoading: false, | ||
} as any) | ||
|
||
mockFcl = createMockFclInstance() | ||
jest.mocked(fcl.createFlowClient).mockReturnValue(mockFcl.mockFclInstance) | ||
}) | ||
|
||
describe("list", () => { | ||
test("lists transactions for an account", async () => { | ||
const mockTransactions = [ | ||
{ | ||
id: "1", | ||
priority: 1, | ||
executionEffort: "100", | ||
status: 0, | ||
fees: "0.001", | ||
scheduledTimestamp: "1234567890.0", | ||
handlerTypeIdentifier: "A.123.Handler", | ||
handlerAddress: "0x123", | ||
}, | ||
] | ||
|
||
jest | ||
.mocked(mockFcl.mockFclInstance.query) | ||
.mockResolvedValueOnce(mockTransactions) | ||
|
||
const {result} = renderHook(() => useFlowSchedule(), { | ||
wrapper: FlowProvider, | ||
}) | ||
|
||
const transactions = await result.current.list("0xACCOUNT") | ||
|
||
expect(transactions).toHaveLength(1) | ||
expect(transactions[0].id).toBe(1n) | ||
expect(transactions[0].priority).toBe(TransactionPriority.Medium) | ||
expect(transactions[0].status).toBe(TransactionStatus.Pending) | ||
expect(mockFcl.mockFclInstance.query).toHaveBeenCalled() | ||
}) | ||
|
||
test("lists transactions with handler data", async () => { | ||
const mockTransactionsWithHandler = [ | ||
{ | ||
id: "1", | ||
priority: 2, | ||
executionEffort: "200", | ||
status: 1, | ||
fees: "0.002", | ||
scheduledTimestamp: "1234567890.0", | ||
handlerTypeIdentifier: "A.456.Handler", | ||
handlerAddress: "0x456", | ||
handlerUUID: "9999", | ||
handlerResolvedViews: {display: {name: "Test"}}, | ||
}, | ||
] | ||
|
||
jest | ||
.mocked(mockFcl.mockFclInstance.query) | ||
.mockResolvedValueOnce(mockTransactionsWithHandler) | ||
|
||
const {result} = renderHook(() => useFlowSchedule(), { | ||
wrapper: FlowProvider, | ||
}) | ||
|
||
const transactions = (await result.current.list("0xACCOUNT", { | ||
includeHandlerData: true, | ||
})) as TransactionInfoWithHandler[] | ||
|
||
expect(transactions).toHaveLength(1) | ||
expect(transactions[0].id).toBe(1n) | ||
expect(transactions[0].handlerUUID).toBe(9999n) | ||
expect(transactions[0].handlerResolvedViews).toEqual({ | ||
display: {name: "Test"}, | ||
}) | ||
expect(mockFcl.mockFclInstance.query).toHaveBeenCalled() | ||
}) | ||
|
||
test("returns empty array when no transactions", async () => { | ||
jest.mocked(mockFcl.mockFclInstance.query).mockResolvedValueOnce([]) | ||
|
||
const {result} = renderHook(() => useFlowSchedule(), { | ||
wrapper: FlowProvider, | ||
}) | ||
|
||
const transactions = await result.current.list("0xACCOUNT") | ||
|
||
expect(transactions).toEqual([]) | ||
expect(mockFcl.mockFclInstance.query).toHaveBeenCalled() | ||
}) | ||
|
||
test("throws error when chain ID not detected", async () => { | ||
jest.mocked(useFlowChainId).mockReturnValue({ | ||
data: null, | ||
isLoading: false, | ||
} as any) | ||
|
||
const {result} = renderHook(() => useFlowSchedule(), { | ||
wrapper: FlowProvider, | ||
}) | ||
|
||
await expect(result.current.list("0xACCOUNT")).rejects.toThrow( | ||
"Chain ID not detected" | ||
) | ||
}) | ||
|
||
test("handles query errors", async () => { | ||
const error = new Error("Query failed") | ||
jest.mocked(mockFcl.mockFclInstance.query).mockRejectedValueOnce(error) | ||
|
||
const {result} = renderHook(() => useFlowSchedule(), { | ||
wrapper: FlowProvider, | ||
}) | ||
|
||
await expect(result.current.list("0xACCOUNT")).rejects.toThrow( | ||
"Failed to list transactions: Query failed" | ||
) | ||
}) | ||
}) | ||
|
||
describe("get", () => { | ||
test("gets a transaction by ID", async () => { | ||
const mockTransaction = { | ||
id: "42", | ||
priority: 0, | ||
executionEffort: "50", | ||
status: 2, | ||
fees: "0.0005", | ||
scheduledTimestamp: "1234567890.0", | ||
handlerTypeIdentifier: "A.789.Handler", | ||
handlerAddress: "0x789", | ||
} | ||
|
||
jest | ||
.mocked(mockFcl.mockFclInstance.query) | ||
.mockResolvedValueOnce(mockTransaction) | ||
|
||
const {result} = renderHook(() => useFlowSchedule(), { | ||
wrapper: FlowProvider, | ||
}) | ||
|
||
const transaction = await result.current.get(42n) | ||
|
||
expect(transaction).toBeDefined() | ||
expect(transaction?.id).toBe(42n) | ||
expect(transaction?.priority).toBe(TransactionPriority.Low) | ||
expect(transaction?.status).toBe(TransactionStatus.Completed) | ||
expect(mockFcl.mockFclInstance.query).toHaveBeenCalled() | ||
}) | ||
|
||
test("gets a transaction with handler data", async () => { | ||
const mockTransactionWithHandler = { | ||
id: "42", | ||
priority: 1, | ||
executionEffort: "100", | ||
status: 0, | ||
fees: "0.001", | ||
scheduledTimestamp: "1234567890.0", | ||
handlerTypeIdentifier: "A.789.Handler", | ||
handlerAddress: "0x789", | ||
handlerUUID: "5555", | ||
handlerResolvedViews: {metadata: {description: "Test handler"}}, | ||
} | ||
|
||
jest | ||
.mocked(mockFcl.mockFclInstance.query) | ||
.mockResolvedValueOnce(mockTransactionWithHandler) | ||
|
||
const {result} = renderHook(() => useFlowSchedule(), { | ||
wrapper: FlowProvider, | ||
}) | ||
|
||
const transaction = (await result.current.get(42n, { | ||
includeHandlerData: true, | ||
})) as TransactionInfoWithHandler | ||
|
||
expect(transaction).toBeDefined() | ||
expect(transaction.id).toBe(42n) | ||
expect(transaction.handlerUUID).toBe(5555n) | ||
expect(transaction.handlerResolvedViews).toEqual({ | ||
metadata: {description: "Test handler"}, | ||
}) | ||
expect(mockFcl.mockFclInstance.query).toHaveBeenCalled() | ||
}) | ||
|
||
test("returns null when transaction not found", async () => { | ||
jest.mocked(mockFcl.mockFclInstance.query).mockResolvedValueOnce(null) | ||
|
||
const {result} = renderHook(() => useFlowSchedule(), { | ||
wrapper: FlowProvider, | ||
}) | ||
|
||
const transaction = await result.current.get(999n) | ||
|
||
expect(transaction).toBeNull() | ||
expect(mockFcl.mockFclInstance.query).toHaveBeenCalled() | ||
}) | ||
|
||
test("handles query errors", async () => { | ||
const error = new Error("Query failed") | ||
jest.mocked(mockFcl.mockFclInstance.query).mockRejectedValueOnce(error) | ||
|
||
const {result} = renderHook(() => useFlowSchedule(), { | ||
wrapper: FlowProvider, | ||
}) | ||
|
||
await expect(result.current.get(42n)).rejects.toThrow( | ||
"Failed to get transaction: Query failed" | ||
) | ||
}) | ||
}) | ||
|
||
describe("setup", () => { | ||
test("sets up manager successfully", async () => { | ||
const txId = "setup-tx-id-123" | ||
jest.mocked(mockFcl.mockFclInstance.mutate).mockResolvedValueOnce(txId) | ||
|
||
const {result} = renderHook(() => useFlowSchedule(), { | ||
wrapper: FlowProvider, | ||
}) | ||
|
||
const returnedTxId = await result.current.setup() | ||
|
||
expect(returnedTxId).toBe(txId) | ||
expect(mockFcl.mockFclInstance.mutate).toHaveBeenCalled() | ||
}) | ||
|
||
test("handles setup errors", async () => { | ||
const error = new Error("Setup failed") | ||
jest.mocked(mockFcl.mockFclInstance.mutate).mockRejectedValueOnce(error) | ||
|
||
const {result} = renderHook(() => useFlowSchedule(), { | ||
wrapper: FlowProvider, | ||
}) | ||
|
||
await expect(result.current.setup()).rejects.toThrow( | ||
"Failed to setup manager: Setup failed" | ||
) | ||
}) | ||
}) | ||
|
||
describe("cancel", () => { | ||
test("cancels a transaction successfully", async () => { | ||
const txId = "cancel-tx-id-456" | ||
jest.mocked(mockFcl.mockFclInstance.mutate).mockResolvedValueOnce(txId) | ||
|
||
const {result} = renderHook(() => useFlowSchedule(), { | ||
wrapper: FlowProvider, | ||
}) | ||
|
||
const returnedTxId = await result.current.cancel(42n) | ||
|
||
expect(returnedTxId).toBe(txId) | ||
expect(mockFcl.mockFclInstance.mutate).toHaveBeenCalled() | ||
}) | ||
|
||
test("handles cancel errors", async () => { | ||
const error = new Error("Cancel failed") | ||
jest.mocked(mockFcl.mockFclInstance.mutate).mockRejectedValueOnce(error) | ||
|
||
const {result} = renderHook(() => useFlowSchedule(), { | ||
wrapper: FlowProvider, | ||
}) | ||
|
||
await expect(result.current.cancel(42n)).rejects.toThrow( | ||
"Failed to cancel transaction: Cancel failed" | ||
) | ||
}) | ||
}) | ||
|
||
test("uses custom flowClient when provided", async () => { | ||
const customMockFcl = createMockFclInstance() | ||
const customFlowClient = customMockFcl.mockFclInstance as any | ||
|
||
jest.mocked(customFlowClient.query).mockResolvedValueOnce([]) | ||
|
||
const {result} = renderHook( | ||
() => useFlowSchedule({flowClient: customFlowClient}), | ||
{ | ||
wrapper: FlowProvider, | ||
} | ||
) | ||
|
||
await result.current.list("0xACCOUNT") | ||
|
||
expect(customFlowClient.query).toHaveBeenCalled() | ||
}) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we make this more descriptive?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Improved 👍