[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill-sui-move-unit-testing":3,"mdc-jd5cob-key":39,"related-org-sui-move-unit-testing":2325,"related-repo-sui-move-unit-testing":2488},{"slug":4,"name":4,"fn":5,"description":6,"org":7,"tags":12,"stars":28,"repoUrl":29,"updatedAt":30,"license":31,"forks":32,"topics":33,"repo":34,"sourceUrl":37,"mdContent":38},"move-unit-testing","write unit tests for Sui Move contracts","Use when writing unit tests for Move smart contracts on Sui. Applies to test function naming, assertions, test attributes, context usage, and cleanup patterns. Use whenever user asks to write tests, add tests, or test a Move module.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},"sui","Sui (Mysten Labs)","https:\u002F\u002Fpexgzepcugksgbtrxkhf.supabase.co\u002Fstorage\u002Fv1\u002Fobject\u002Fpublic\u002Forg-logos\u002Fsui.png","MystenLabs",[13,16,19,22,25],{"name":14,"slug":8,"type":15},"Sui","tag",{"name":17,"slug":18,"type":15},"QA","qa",{"name":20,"slug":21,"type":15},"Web3","web3",{"name":23,"slug":24,"type":15},"Smart Contracts","smart-contracts",{"name":26,"slug":27,"type":15},"Testing","testing",9,"https:\u002F\u002Fgithub.com\u002FMystenLabs\u002Fskills","2026-08-01T05:44:30.788585",null,2,[],{"repoUrl":29,"stars":28,"forks":32,"topics":35,"description":36},[],"Sui development skills maintained by Mysten Labs","https:\u002F\u002Fgithub.com\u002FMystenLabs\u002Fskills\u002Ftree\u002FHEAD\u002Fmove-unit-testing","---\nname: move-unit-testing\ndescription: Use when writing unit tests for Move smart contracts on Sui. Applies to test function naming, assertions, test attributes, context usage, and cleanup patterns. Use whenever user asks to write tests, add tests, or test a Move module.\n---\n\n# move-unit-testing\n\n> **MCP tool:** When available in your environment, also query the Sui documentation MCP server (`https:\u002F\u002Fsui.mcp.kapa.ai`) for up-to-date answers. Use it for verification and for details not covered by these reference files.\n\n## Overview\n\nAI agents consistently use outdated or suboptimal patterns when writing Move unit tests. This skill covers the correct testing conventions from the official Sui Move code quality checklist and testing documentation.\n\nAll patterns sourced from https:\u002F\u002Fmove-book.com\u002Fguides\u002Fcode-quality-checklist and https:\u002F\u002Fmove-book.com\u002Ftesting\u002F\n\n## No `test_` Prefix in Test Modules\n\nTest functions inside `_tests` modules should NOT be prefixed with `test_`. The module name already indicates these are tests. Use descriptive names that read as statements.\n\n```move\n\u002F\u002F WRONG — redundant prefix\nmodule my_package::my_module_tests;\n\n#[test]\nfun test_create_pool() { \u002F* ... *\u002F }\n\n#[test]\nfun test_swap_fails_on_zero() { \u002F* ... *\u002F }\n\n\u002F\u002F CORRECT — descriptive statement names\nmodule my_package::my_module_tests;\n\n#[test]\nfun create_pool_with_initial_liquidity() { \u002F* ... *\u002F }\n\n#[test]\nfun swap_aborts_on_zero_input() { \u002F* ... *\u002F }\n```\n\n## Use `assert_eq!` Instead of `assert!` for Comparisons\n\n`assert_eq!` displays both values on failure, making debugging much easier. Never use `assert!(x == y)` or `assert!(x == y, 0)` for equality checks.\n\n```move\n\u002F\u002F WRONG — no diagnostic info on failure\nassert!(result == 100);\nassert!(result == expected_value, 0);\n\n\u002F\u002F CORRECT — shows both values on failure\nuse std::unit_test::assert_eq;\n\nassert_eq!(result, 100);\nassert_eq!(result, expected_value);\n```\n\nUse plain `assert!` only for boolean conditions where there's nothing to compare:\n\n```move\n\u002F\u002F assert! is fine for boolean checks\nassert!(is_valid);\nassert!(vec.length() > 0);\n```\n\n## No Abort Codes in Test `assert!`\n\nDo not pass numeric abort codes to `assert!` in tests. They can accidentally match application error codes and confuse debugging.\n\n```move\n\u002F\u002F WRONG — numeric code may collide with app errors\nassert!(is_success, 0);\nassert!(balance > 0, 1);\n\n\u002F\u002F CORRECT — no abort code\nassert!(is_success);\nassert!(balance > 0);\n```\n\n## Merge `#[test]` and `#[expected_failure]` on One Line\n\n```move\n\u002F\u002F WRONG — separate attributes\n#[test]\n#[expected_failure(abort_code = EInvalidInput, location = my_app)]\nfun invalid_input_aborts() { \u002F* ... *\u002F }\n\n\u002F\u002F CORRECT — merged on one line\n#[test, expected_failure(abort_code = EInvalidInput, location = my_app)]\nfun invalid_input_aborts() { \u002F* ... *\u002F }\n```\n\n## `expected_failure` with `location` for cross-module aborts\n\nWhen the abort happens in a different module than the test, you **must** specify `location`. Without it, the test framework expects the abort to originate in the test module and the test fails.\n\n```move\n\u002F\u002F Test module: my_package::app_tests\n\u002F\u002F Abort happens in: my_package::app\n\nconst ENotAuthorized: u64 = 0;  \u002F\u002F mirror the constant value from app module\n\n\u002F\u002F WRONG — no location; test fails because abort comes from `app`, not `app_tests`\n#[test, expected_failure(abort_code = ENotAuthorized)]\nfun unauthorized_call_aborts() { \u002F* ... *\u002F }\n\n\u002F\u002F CORRECT — location points to the module where the abort originates\n#[test, expected_failure(abort_code = ENotAuthorized, location = app)]\nfun unauthorized_call_aborts() { \u002F* ... *\u002F }\n```\n\nThe `location` value is just the module name (e.g., `app`), not the fully qualified path (`my_package::app`). Using the fully qualified form causes a compile error: \"Unexpected module member identifier.\"\n\n## Skip Cleanup in `expected_failure` Tests\n\nTests annotated with `expected_failure` will abort — any cleanup code after the abort point is dead code. Don't call `.end()` or destroy objects after the expected abort.\n\n```move\n\u002F\u002F WRONG — cleanup after abort is dead code\n#[test, expected_failure(abort_code = my_app::EInsufficientBalance)]\nfun withdraw_more_than_balance_aborts() {\n    let mut scenario = test_scenario::begin(@0xA);\n    my_app::withdraw(1000, scenario.ctx());\n    scenario.end(); \u002F\u002F never reached\n}\n\n\u002F\u002F CORRECT — let it abort naturally\n#[test, expected_failure(abort_code = my_app::EInsufficientBalance)]\nfun withdraw_more_than_balance_aborts() {\n    let mut scenario = test_scenario::begin(@0xA);\n    my_app::withdraw(1000, scenario.ctx());\n    \u002F\u002F no cleanup needed — test aborts above\n}\n```\n\n## Use `tx_context::dummy()` for Simple Tests\n\nIf a test only needs a `TxContext` and doesn't need multi-transaction simulation, use `tx_context::dummy()` instead of a full `test_scenario`. Reserve `test_scenario` for tests that actually need to simulate multiple transactions, shared objects, or transfers between addresses.\n\n```move\n\u002F\u002F WRONG — unnecessary overhead for a simple test\n#[test]\nfun mint_returns_correct_value() {\n    let mut scenario = test_scenario::begin(@0xA);\n    let item = app::create_item(100, scenario.ctx());\n    assert_eq!(item.value(), 100);\n    std::unit_test::destroy(item);\n    scenario.end();\n}\n\n\u002F\u002F CORRECT — dummy context is sufficient\n#[test]\nfun mint_returns_correct_value() {\n    let ctx = &mut tx_context::dummy();\n    let item = app::create_item(100, ctx);\n    assert_eq!(item.value(), 100);\n    std::unit_test::destroy(item);\n}\n```\n\n**When to use `test_scenario`:** shared objects, multi-transaction flows, testing transfers between addresses, testing `init` functions, epoch\u002Ftime manipulation.\n\n**When to use `tx_context::dummy()`:** pure function tests, single-operation tests, anything that just needs a ctx to create objects.\n\n## `test_scenario` for Multi-Transaction and Authorization Tests\n\nUse `test_scenario` when you need to simulate multiple transactions, different senders, shared objects, or test `init` functions. The core API:\n\n| Function | Purpose |\n|---|---|\n| `test_scenario::begin(@addr)` | Start a scenario with `@addr` as the first sender |\n| `scenario.next_tx(@addr)` | Advance to a new transaction with `@addr` as sender |\n| `scenario.take_from_sender\u003CT>()` | Take an owned object sent to the current sender |\n| `scenario.return_to_sender(obj)` | Return an owned object to the current sender |\n| `scenario.take_shared\u003CT>()` | Take a shared object by type |\n| `test_scenario::return_shared(obj)` | Return a shared object |\n| `scenario.has_most_recent_for_sender\u003CT>()` | Check if sender has an object of type `T` |\n| `scenario.end()` | Finalize the scenario (must be called in non-aborting tests) |\n\n### Success test — create and verify\n\n```move\n#[test]\nfun owner_can_update_item() {\n    let owner = @0xA;\n    let mut scenario = test_scenario::begin(owner);\n\n    \u002F\u002F Tx 1: create an item (transferred to owner inside create_item)\n    app::create_item(b\"sword\".to_string(), scenario.ctx());\n\n    \u002F\u002F Tx 2: owner takes the item and updates it\n    scenario.next_tx(owner);\n    let mut item = scenario.take_from_sender\u003CItem>();\n    app::set_name(&mut item, b\"great sword\".to_string());\n    assert_eq!(app::name(&item), b\"great sword\".to_string());\n    scenario.return_to_sender(item);\n\n    scenario.end();\n}\n```\n\n### Unauthorized caller test\n\n```move\n#[test, expected_failure(abort_code = app::ENotOwner, location = app)]\nfun non_owner_cannot_update_item() {\n    let owner = @0xA;\n    let attacker = @0xB;\n    let mut scenario = test_scenario::begin(owner);\n\n    \u002F\u002F Tx 1: owner creates a shared item\n    app::create_shared_item(b\"shield\".to_string(), scenario.ctx());\n\n    \u002F\u002F Tx 2: attacker tries to update it — should abort\n    scenario.next_tx(attacker);\n    let mut item = scenario.take_shared\u003CItem>();\n    app::admin_update(&mut item, b\"hacked\".to_string(), scenario.ctx());\n    \u002F\u002F no cleanup — test aborts above\n}\n```\n\n### Shared object test\n\n```move\n#[test]\nfun shared_counter_increments() {\n    let mut scenario = test_scenario::begin(@0xA);\n\n    \u002F\u002F Tx 1: create and share\n    app::create_counter(scenario.ctx());\n\n    \u002F\u002F Tx 2: anyone can increment\n    scenario.next_tx(@0xB);\n    let mut counter = scenario.take_shared\u003CCounter>();\n    app::increment(&mut counter);\n    assert_eq!(app::value(&counter), 1);\n    test_scenario::return_shared(counter);\n\n    scenario.end();\n}\n```\n\n### Testing `init` functions\n\n```move\n#[test]\nfun init_creates_admin_cap() {\n    let mut scenario = test_scenario::begin(@0xA);\n\n    \u002F\u002F init is called automatically for the first tx in begin()\n    \u002F\u002F if the module has an init function — but in tests you call it explicitly:\n    app::init_for_testing(scenario.ctx());\n\n    scenario.next_tx(@0xA);\n    assert!(scenario.has_most_recent_for_sender\u003CAdminCap>());\n\n    scenario.end();\n}\n```\n\nNote: modules typically expose a `init_for_testing` or `test_init` helper since `init` itself is not directly callable in tests. Use `#[test_only]` to gate these helpers.\n\n## Use `std::unit_test::destroy` for Cleanup\n\nUse `std::unit_test::destroy` to clean up test objects. The old `sui::test_utils::destroy` is **deprecated**.\n\n```move\n\u002F\u002F WRONG — deprecated\nuse sui::test_utils::destroy;\n\n\u002F\u002F WRONG — custom cleanup functions\nnft.destroy_for_testing();\n\n\u002F\u002F CORRECT — use std::unit_test::destroy\nuse std::unit_test::destroy;\n\ndestroy(nft);\ndestroy(app);\n```\n\n## Quick Reference\n\n| Pattern | Correct | Common Mistake |\n|---------|---------|----------------|\n| Test function naming | `create_pool_succeeds()` | `test_create_pool()` |\n| Equality assertions | `assert_eq!(x, 100)` | `assert!(x == 100, 0)` |\n| Boolean assertions | `assert!(is_valid)` | `assert!(is_valid, 0)` |\n| Test attributes | `#[test, expected_failure(...)]` | Separate `#[test]` and `#[expected_failure]` |\n| Expected failure cleanup | Let it abort, no cleanup | Calling `.end()` after abort |\n| Simple test context | `tx_context::dummy()` | Full `test_scenario` for simple tests |\n| Object cleanup | `test_utils::destroy(obj)` | `obj.destroy_for_testing()` |\n",{"data":40,"body":41},{"name":4,"description":6},{"type":42,"children":43},"root",[44,51,76,83,88,109,123,143,300,322,348,425,437,468,479,491,553,574,642,662,681,780,808,821,841,962,975,1010,1153,1178,1193,1204,1222,1407,1414,1551,1557,1679,1685,1813,1826,1930,1965,1978,2004,2096,2102,2319],{"type":45,"tag":46,"props":47,"children":48},"element","h1",{"id":4},[49],{"type":50,"value":4},"text",{"type":45,"tag":52,"props":53,"children":54},"blockquote",{},[55],{"type":45,"tag":56,"props":57,"children":58},"p",{},[59,65,67,74],{"type":45,"tag":60,"props":61,"children":62},"strong",{},[63],{"type":50,"value":64},"MCP tool:",{"type":50,"value":66}," When available in your environment, also query the Sui documentation MCP server (",{"type":45,"tag":68,"props":69,"children":71},"code",{"className":70},[],[72],{"type":50,"value":73},"https:\u002F\u002Fsui.mcp.kapa.ai",{"type":50,"value":75},") for up-to-date answers. Use it for verification and for details not covered by these reference files.",{"type":45,"tag":77,"props":78,"children":80},"h2",{"id":79},"overview",[81],{"type":50,"value":82},"Overview",{"type":45,"tag":56,"props":84,"children":85},{},[86],{"type":50,"value":87},"AI agents consistently use outdated or suboptimal patterns when writing Move unit tests. This skill covers the correct testing conventions from the official Sui Move code quality checklist and testing documentation.",{"type":45,"tag":56,"props":89,"children":90},{},[91,93,101,103],{"type":50,"value":92},"All patterns sourced from ",{"type":45,"tag":94,"props":95,"children":99},"a",{"href":96,"rel":97},"https:\u002F\u002Fmove-book.com\u002Fguides\u002Fcode-quality-checklist",[98],"nofollow",[100],{"type":50,"value":96},{"type":50,"value":102}," and ",{"type":45,"tag":94,"props":104,"children":107},{"href":105,"rel":106},"https:\u002F\u002Fmove-book.com\u002Ftesting\u002F",[98],[108],{"type":50,"value":105},{"type":45,"tag":77,"props":110,"children":112},{"id":111},"no-test_-prefix-in-test-modules",[113,115,121],{"type":50,"value":114},"No ",{"type":45,"tag":68,"props":116,"children":118},{"className":117},[],[119],{"type":50,"value":120},"test_",{"type":50,"value":122}," Prefix in Test Modules",{"type":45,"tag":56,"props":124,"children":125},{},[126,128,134,136,141],{"type":50,"value":127},"Test functions inside ",{"type":45,"tag":68,"props":129,"children":131},{"className":130},[],[132],{"type":50,"value":133},"_tests",{"type":50,"value":135}," modules should NOT be prefixed with ",{"type":45,"tag":68,"props":137,"children":139},{"className":138},[],[140],{"type":50,"value":120},{"type":50,"value":142},". The module name already indicates these are tests. Use descriptive names that read as statements.",{"type":45,"tag":144,"props":145,"children":150},"pre",{"className":146,"code":147,"language":148,"meta":149,"style":149},"language-move shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F\u002F WRONG — redundant prefix\nmodule my_package::my_module_tests;\n\n#[test]\nfun test_create_pool() { \u002F* ... *\u002F }\n\n#[test]\nfun test_swap_fails_on_zero() { \u002F* ... *\u002F }\n\n\u002F\u002F CORRECT — descriptive statement names\nmodule my_package::my_module_tests;\n\n#[test]\nfun create_pool_with_initial_liquidity() { \u002F* ... *\u002F }\n\n#[test]\nfun swap_aborts_on_zero_input() { \u002F* ... *\u002F }\n","move","",[151],{"type":45,"tag":68,"props":152,"children":153},{"__ignoreMap":149},[154,165,173,183,192,201,209,217,226,233,242,250,258,266,275,283,291],{"type":45,"tag":155,"props":156,"children":159},"span",{"class":157,"line":158},"line",1,[160],{"type":45,"tag":155,"props":161,"children":162},{},[163],{"type":50,"value":164},"\u002F\u002F WRONG — redundant prefix\n",{"type":45,"tag":155,"props":166,"children":167},{"class":157,"line":32},[168],{"type":45,"tag":155,"props":169,"children":170},{},[171],{"type":50,"value":172},"module my_package::my_module_tests;\n",{"type":45,"tag":155,"props":174,"children":176},{"class":157,"line":175},3,[177],{"type":45,"tag":155,"props":178,"children":180},{"emptyLinePlaceholder":179},true,[181],{"type":50,"value":182},"\n",{"type":45,"tag":155,"props":184,"children":186},{"class":157,"line":185},4,[187],{"type":45,"tag":155,"props":188,"children":189},{},[190],{"type":50,"value":191},"#[test]\n",{"type":45,"tag":155,"props":193,"children":195},{"class":157,"line":194},5,[196],{"type":45,"tag":155,"props":197,"children":198},{},[199],{"type":50,"value":200},"fun test_create_pool() { \u002F* ... *\u002F }\n",{"type":45,"tag":155,"props":202,"children":204},{"class":157,"line":203},6,[205],{"type":45,"tag":155,"props":206,"children":207},{"emptyLinePlaceholder":179},[208],{"type":50,"value":182},{"type":45,"tag":155,"props":210,"children":212},{"class":157,"line":211},7,[213],{"type":45,"tag":155,"props":214,"children":215},{},[216],{"type":50,"value":191},{"type":45,"tag":155,"props":218,"children":220},{"class":157,"line":219},8,[221],{"type":45,"tag":155,"props":222,"children":223},{},[224],{"type":50,"value":225},"fun test_swap_fails_on_zero() { \u002F* ... *\u002F }\n",{"type":45,"tag":155,"props":227,"children":228},{"class":157,"line":28},[229],{"type":45,"tag":155,"props":230,"children":231},{"emptyLinePlaceholder":179},[232],{"type":50,"value":182},{"type":45,"tag":155,"props":234,"children":236},{"class":157,"line":235},10,[237],{"type":45,"tag":155,"props":238,"children":239},{},[240],{"type":50,"value":241},"\u002F\u002F CORRECT — descriptive statement names\n",{"type":45,"tag":155,"props":243,"children":245},{"class":157,"line":244},11,[246],{"type":45,"tag":155,"props":247,"children":248},{},[249],{"type":50,"value":172},{"type":45,"tag":155,"props":251,"children":253},{"class":157,"line":252},12,[254],{"type":45,"tag":155,"props":255,"children":256},{"emptyLinePlaceholder":179},[257],{"type":50,"value":182},{"type":45,"tag":155,"props":259,"children":261},{"class":157,"line":260},13,[262],{"type":45,"tag":155,"props":263,"children":264},{},[265],{"type":50,"value":191},{"type":45,"tag":155,"props":267,"children":269},{"class":157,"line":268},14,[270],{"type":45,"tag":155,"props":271,"children":272},{},[273],{"type":50,"value":274},"fun create_pool_with_initial_liquidity() { \u002F* ... *\u002F }\n",{"type":45,"tag":155,"props":276,"children":278},{"class":157,"line":277},15,[279],{"type":45,"tag":155,"props":280,"children":281},{"emptyLinePlaceholder":179},[282],{"type":50,"value":182},{"type":45,"tag":155,"props":284,"children":286},{"class":157,"line":285},16,[287],{"type":45,"tag":155,"props":288,"children":289},{},[290],{"type":50,"value":191},{"type":45,"tag":155,"props":292,"children":294},{"class":157,"line":293},17,[295],{"type":45,"tag":155,"props":296,"children":297},{},[298],{"type":50,"value":299},"fun swap_aborts_on_zero_input() { \u002F* ... *\u002F }\n",{"type":45,"tag":77,"props":301,"children":303},{"id":302},"use-assert_eq-instead-of-assert-for-comparisons",[304,306,312,314,320],{"type":50,"value":305},"Use ",{"type":45,"tag":68,"props":307,"children":309},{"className":308},[],[310],{"type":50,"value":311},"assert_eq!",{"type":50,"value":313}," Instead of ",{"type":45,"tag":68,"props":315,"children":317},{"className":316},[],[318],{"type":50,"value":319},"assert!",{"type":50,"value":321}," for Comparisons",{"type":45,"tag":56,"props":323,"children":324},{},[325,330,332,338,340,346],{"type":45,"tag":68,"props":326,"children":328},{"className":327},[],[329],{"type":50,"value":311},{"type":50,"value":331}," displays both values on failure, making debugging much easier. Never use ",{"type":45,"tag":68,"props":333,"children":335},{"className":334},[],[336],{"type":50,"value":337},"assert!(x == y)",{"type":50,"value":339}," or ",{"type":45,"tag":68,"props":341,"children":343},{"className":342},[],[344],{"type":50,"value":345},"assert!(x == y, 0)",{"type":50,"value":347}," for equality checks.",{"type":45,"tag":144,"props":349,"children":351},{"className":146,"code":350,"language":148,"meta":149,"style":149},"\u002F\u002F WRONG — no diagnostic info on failure\nassert!(result == 100);\nassert!(result == expected_value, 0);\n\n\u002F\u002F CORRECT — shows both values on failure\nuse std::unit_test::assert_eq;\n\nassert_eq!(result, 100);\nassert_eq!(result, expected_value);\n",[352],{"type":45,"tag":68,"props":353,"children":354},{"__ignoreMap":149},[355,363,371,379,386,394,402,409,417],{"type":45,"tag":155,"props":356,"children":357},{"class":157,"line":158},[358],{"type":45,"tag":155,"props":359,"children":360},{},[361],{"type":50,"value":362},"\u002F\u002F WRONG — no diagnostic info on failure\n",{"type":45,"tag":155,"props":364,"children":365},{"class":157,"line":32},[366],{"type":45,"tag":155,"props":367,"children":368},{},[369],{"type":50,"value":370},"assert!(result == 100);\n",{"type":45,"tag":155,"props":372,"children":373},{"class":157,"line":175},[374],{"type":45,"tag":155,"props":375,"children":376},{},[377],{"type":50,"value":378},"assert!(result == expected_value, 0);\n",{"type":45,"tag":155,"props":380,"children":381},{"class":157,"line":185},[382],{"type":45,"tag":155,"props":383,"children":384},{"emptyLinePlaceholder":179},[385],{"type":50,"value":182},{"type":45,"tag":155,"props":387,"children":388},{"class":157,"line":194},[389],{"type":45,"tag":155,"props":390,"children":391},{},[392],{"type":50,"value":393},"\u002F\u002F CORRECT — shows both values on failure\n",{"type":45,"tag":155,"props":395,"children":396},{"class":157,"line":203},[397],{"type":45,"tag":155,"props":398,"children":399},{},[400],{"type":50,"value":401},"use std::unit_test::assert_eq;\n",{"type":45,"tag":155,"props":403,"children":404},{"class":157,"line":211},[405],{"type":45,"tag":155,"props":406,"children":407},{"emptyLinePlaceholder":179},[408],{"type":50,"value":182},{"type":45,"tag":155,"props":410,"children":411},{"class":157,"line":219},[412],{"type":45,"tag":155,"props":413,"children":414},{},[415],{"type":50,"value":416},"assert_eq!(result, 100);\n",{"type":45,"tag":155,"props":418,"children":419},{"class":157,"line":28},[420],{"type":45,"tag":155,"props":421,"children":422},{},[423],{"type":50,"value":424},"assert_eq!(result, expected_value);\n",{"type":45,"tag":56,"props":426,"children":427},{},[428,430,435],{"type":50,"value":429},"Use plain ",{"type":45,"tag":68,"props":431,"children":433},{"className":432},[],[434],{"type":50,"value":319},{"type":50,"value":436}," only for boolean conditions where there's nothing to compare:",{"type":45,"tag":144,"props":438,"children":440},{"className":146,"code":439,"language":148,"meta":149,"style":149},"\u002F\u002F assert! is fine for boolean checks\nassert!(is_valid);\nassert!(vec.length() > 0);\n",[441],{"type":45,"tag":68,"props":442,"children":443},{"__ignoreMap":149},[444,452,460],{"type":45,"tag":155,"props":445,"children":446},{"class":157,"line":158},[447],{"type":45,"tag":155,"props":448,"children":449},{},[450],{"type":50,"value":451},"\u002F\u002F assert! is fine for boolean checks\n",{"type":45,"tag":155,"props":453,"children":454},{"class":157,"line":32},[455],{"type":45,"tag":155,"props":456,"children":457},{},[458],{"type":50,"value":459},"assert!(is_valid);\n",{"type":45,"tag":155,"props":461,"children":462},{"class":157,"line":175},[463],{"type":45,"tag":155,"props":464,"children":465},{},[466],{"type":50,"value":467},"assert!(vec.length() > 0);\n",{"type":45,"tag":77,"props":469,"children":471},{"id":470},"no-abort-codes-in-test-assert",[472,474],{"type":50,"value":473},"No Abort Codes in Test ",{"type":45,"tag":68,"props":475,"children":477},{"className":476},[],[478],{"type":50,"value":319},{"type":45,"tag":56,"props":480,"children":481},{},[482,484,489],{"type":50,"value":483},"Do not pass numeric abort codes to ",{"type":45,"tag":68,"props":485,"children":487},{"className":486},[],[488],{"type":50,"value":319},{"type":50,"value":490}," in tests. They can accidentally match application error codes and confuse debugging.",{"type":45,"tag":144,"props":492,"children":494},{"className":146,"code":493,"language":148,"meta":149,"style":149},"\u002F\u002F WRONG — numeric code may collide with app errors\nassert!(is_success, 0);\nassert!(balance > 0, 1);\n\n\u002F\u002F CORRECT — no abort code\nassert!(is_success);\nassert!(balance > 0);\n",[495],{"type":45,"tag":68,"props":496,"children":497},{"__ignoreMap":149},[498,506,514,522,529,537,545],{"type":45,"tag":155,"props":499,"children":500},{"class":157,"line":158},[501],{"type":45,"tag":155,"props":502,"children":503},{},[504],{"type":50,"value":505},"\u002F\u002F WRONG — numeric code may collide with app errors\n",{"type":45,"tag":155,"props":507,"children":508},{"class":157,"line":32},[509],{"type":45,"tag":155,"props":510,"children":511},{},[512],{"type":50,"value":513},"assert!(is_success, 0);\n",{"type":45,"tag":155,"props":515,"children":516},{"class":157,"line":175},[517],{"type":45,"tag":155,"props":518,"children":519},{},[520],{"type":50,"value":521},"assert!(balance > 0, 1);\n",{"type":45,"tag":155,"props":523,"children":524},{"class":157,"line":185},[525],{"type":45,"tag":155,"props":526,"children":527},{"emptyLinePlaceholder":179},[528],{"type":50,"value":182},{"type":45,"tag":155,"props":530,"children":531},{"class":157,"line":194},[532],{"type":45,"tag":155,"props":533,"children":534},{},[535],{"type":50,"value":536},"\u002F\u002F CORRECT — no abort code\n",{"type":45,"tag":155,"props":538,"children":539},{"class":157,"line":203},[540],{"type":45,"tag":155,"props":541,"children":542},{},[543],{"type":50,"value":544},"assert!(is_success);\n",{"type":45,"tag":155,"props":546,"children":547},{"class":157,"line":211},[548],{"type":45,"tag":155,"props":549,"children":550},{},[551],{"type":50,"value":552},"assert!(balance > 0);\n",{"type":45,"tag":77,"props":554,"children":556},{"id":555},"merge-test-and-expected_failure-on-one-line",[557,559,565,566,572],{"type":50,"value":558},"Merge ",{"type":45,"tag":68,"props":560,"children":562},{"className":561},[],[563],{"type":50,"value":564},"#[test]",{"type":50,"value":102},{"type":45,"tag":68,"props":567,"children":569},{"className":568},[],[570],{"type":50,"value":571},"#[expected_failure]",{"type":50,"value":573}," on One Line",{"type":45,"tag":144,"props":575,"children":577},{"className":146,"code":576,"language":148,"meta":149,"style":149},"\u002F\u002F WRONG — separate attributes\n#[test]\n#[expected_failure(abort_code = EInvalidInput, location = my_app)]\nfun invalid_input_aborts() { \u002F* ... *\u002F }\n\n\u002F\u002F CORRECT — merged on one line\n#[test, expected_failure(abort_code = EInvalidInput, location = my_app)]\nfun invalid_input_aborts() { \u002F* ... *\u002F }\n",[578],{"type":45,"tag":68,"props":579,"children":580},{"__ignoreMap":149},[581,589,596,604,612,619,627,635],{"type":45,"tag":155,"props":582,"children":583},{"class":157,"line":158},[584],{"type":45,"tag":155,"props":585,"children":586},{},[587],{"type":50,"value":588},"\u002F\u002F WRONG — separate attributes\n",{"type":45,"tag":155,"props":590,"children":591},{"class":157,"line":32},[592],{"type":45,"tag":155,"props":593,"children":594},{},[595],{"type":50,"value":191},{"type":45,"tag":155,"props":597,"children":598},{"class":157,"line":175},[599],{"type":45,"tag":155,"props":600,"children":601},{},[602],{"type":50,"value":603},"#[expected_failure(abort_code = EInvalidInput, location = my_app)]\n",{"type":45,"tag":155,"props":605,"children":606},{"class":157,"line":185},[607],{"type":45,"tag":155,"props":608,"children":609},{},[610],{"type":50,"value":611},"fun invalid_input_aborts() { \u002F* ... *\u002F }\n",{"type":45,"tag":155,"props":613,"children":614},{"class":157,"line":194},[615],{"type":45,"tag":155,"props":616,"children":617},{"emptyLinePlaceholder":179},[618],{"type":50,"value":182},{"type":45,"tag":155,"props":620,"children":621},{"class":157,"line":203},[622],{"type":45,"tag":155,"props":623,"children":624},{},[625],{"type":50,"value":626},"\u002F\u002F CORRECT — merged on one line\n",{"type":45,"tag":155,"props":628,"children":629},{"class":157,"line":211},[630],{"type":45,"tag":155,"props":631,"children":632},{},[633],{"type":50,"value":634},"#[test, expected_failure(abort_code = EInvalidInput, location = my_app)]\n",{"type":45,"tag":155,"props":636,"children":637},{"class":157,"line":219},[638],{"type":45,"tag":155,"props":639,"children":640},{},[641],{"type":50,"value":611},{"type":45,"tag":77,"props":643,"children":645},{"id":644},"expected_failure-with-location-for-cross-module-aborts",[646,652,654,660],{"type":45,"tag":68,"props":647,"children":649},{"className":648},[],[650],{"type":50,"value":651},"expected_failure",{"type":50,"value":653}," with ",{"type":45,"tag":68,"props":655,"children":657},{"className":656},[],[658],{"type":50,"value":659},"location",{"type":50,"value":661}," for cross-module aborts",{"type":45,"tag":56,"props":663,"children":664},{},[665,667,672,674,679],{"type":50,"value":666},"When the abort happens in a different module than the test, you ",{"type":45,"tag":60,"props":668,"children":669},{},[670],{"type":50,"value":671},"must",{"type":50,"value":673}," specify ",{"type":45,"tag":68,"props":675,"children":677},{"className":676},[],[678],{"type":50,"value":659},{"type":50,"value":680},". Without it, the test framework expects the abort to originate in the test module and the test fails.",{"type":45,"tag":144,"props":682,"children":684},{"className":146,"code":683,"language":148,"meta":149,"style":149},"\u002F\u002F Test module: my_package::app_tests\n\u002F\u002F Abort happens in: my_package::app\n\nconst ENotAuthorized: u64 = 0;  \u002F\u002F mirror the constant value from app module\n\n\u002F\u002F WRONG — no location; test fails because abort comes from `app`, not `app_tests`\n#[test, expected_failure(abort_code = ENotAuthorized)]\nfun unauthorized_call_aborts() { \u002F* ... *\u002F }\n\n\u002F\u002F CORRECT — location points to the module where the abort originates\n#[test, expected_failure(abort_code = ENotAuthorized, location = app)]\nfun unauthorized_call_aborts() { \u002F* ... *\u002F }\n",[685],{"type":45,"tag":68,"props":686,"children":687},{"__ignoreMap":149},[688,696,704,711,719,726,734,742,750,757,765,773],{"type":45,"tag":155,"props":689,"children":690},{"class":157,"line":158},[691],{"type":45,"tag":155,"props":692,"children":693},{},[694],{"type":50,"value":695},"\u002F\u002F Test module: my_package::app_tests\n",{"type":45,"tag":155,"props":697,"children":698},{"class":157,"line":32},[699],{"type":45,"tag":155,"props":700,"children":701},{},[702],{"type":50,"value":703},"\u002F\u002F Abort happens in: my_package::app\n",{"type":45,"tag":155,"props":705,"children":706},{"class":157,"line":175},[707],{"type":45,"tag":155,"props":708,"children":709},{"emptyLinePlaceholder":179},[710],{"type":50,"value":182},{"type":45,"tag":155,"props":712,"children":713},{"class":157,"line":185},[714],{"type":45,"tag":155,"props":715,"children":716},{},[717],{"type":50,"value":718},"const ENotAuthorized: u64 = 0;  \u002F\u002F mirror the constant value from app module\n",{"type":45,"tag":155,"props":720,"children":721},{"class":157,"line":194},[722],{"type":45,"tag":155,"props":723,"children":724},{"emptyLinePlaceholder":179},[725],{"type":50,"value":182},{"type":45,"tag":155,"props":727,"children":728},{"class":157,"line":203},[729],{"type":45,"tag":155,"props":730,"children":731},{},[732],{"type":50,"value":733},"\u002F\u002F WRONG — no location; test fails because abort comes from `app`, not `app_tests`\n",{"type":45,"tag":155,"props":735,"children":736},{"class":157,"line":211},[737],{"type":45,"tag":155,"props":738,"children":739},{},[740],{"type":50,"value":741},"#[test, expected_failure(abort_code = ENotAuthorized)]\n",{"type":45,"tag":155,"props":743,"children":744},{"class":157,"line":219},[745],{"type":45,"tag":155,"props":746,"children":747},{},[748],{"type":50,"value":749},"fun unauthorized_call_aborts() { \u002F* ... *\u002F }\n",{"type":45,"tag":155,"props":751,"children":752},{"class":157,"line":28},[753],{"type":45,"tag":155,"props":754,"children":755},{"emptyLinePlaceholder":179},[756],{"type":50,"value":182},{"type":45,"tag":155,"props":758,"children":759},{"class":157,"line":235},[760],{"type":45,"tag":155,"props":761,"children":762},{},[763],{"type":50,"value":764},"\u002F\u002F CORRECT — location points to the module where the abort originates\n",{"type":45,"tag":155,"props":766,"children":767},{"class":157,"line":244},[768],{"type":45,"tag":155,"props":769,"children":770},{},[771],{"type":50,"value":772},"#[test, expected_failure(abort_code = ENotAuthorized, location = app)]\n",{"type":45,"tag":155,"props":774,"children":775},{"class":157,"line":252},[776],{"type":45,"tag":155,"props":777,"children":778},{},[779],{"type":50,"value":749},{"type":45,"tag":56,"props":781,"children":782},{},[783,785,790,792,798,800,806],{"type":50,"value":784},"The ",{"type":45,"tag":68,"props":786,"children":788},{"className":787},[],[789],{"type":50,"value":659},{"type":50,"value":791}," value is just the module name (e.g., ",{"type":45,"tag":68,"props":793,"children":795},{"className":794},[],[796],{"type":50,"value":797},"app",{"type":50,"value":799},"), not the fully qualified path (",{"type":45,"tag":68,"props":801,"children":803},{"className":802},[],[804],{"type":50,"value":805},"my_package::app",{"type":50,"value":807},"). Using the fully qualified form causes a compile error: \"Unexpected module member identifier.\"",{"type":45,"tag":77,"props":809,"children":811},{"id":810},"skip-cleanup-in-expected_failure-tests",[812,814,819],{"type":50,"value":813},"Skip Cleanup in ",{"type":45,"tag":68,"props":815,"children":817},{"className":816},[],[818],{"type":50,"value":651},{"type":50,"value":820}," Tests",{"type":45,"tag":56,"props":822,"children":823},{},[824,826,831,833,839],{"type":50,"value":825},"Tests annotated with ",{"type":45,"tag":68,"props":827,"children":829},{"className":828},[],[830],{"type":50,"value":651},{"type":50,"value":832}," will abort — any cleanup code after the abort point is dead code. Don't call ",{"type":45,"tag":68,"props":834,"children":836},{"className":835},[],[837],{"type":50,"value":838},".end()",{"type":50,"value":840}," or destroy objects after the expected abort.",{"type":45,"tag":144,"props":842,"children":844},{"className":146,"code":843,"language":148,"meta":149,"style":149},"\u002F\u002F WRONG — cleanup after abort is dead code\n#[test, expected_failure(abort_code = my_app::EInsufficientBalance)]\nfun withdraw_more_than_balance_aborts() {\n    let mut scenario = test_scenario::begin(@0xA);\n    my_app::withdraw(1000, scenario.ctx());\n    scenario.end(); \u002F\u002F never reached\n}\n\n\u002F\u002F CORRECT — let it abort naturally\n#[test, expected_failure(abort_code = my_app::EInsufficientBalance)]\nfun withdraw_more_than_balance_aborts() {\n    let mut scenario = test_scenario::begin(@0xA);\n    my_app::withdraw(1000, scenario.ctx());\n    \u002F\u002F no cleanup needed — test aborts above\n}\n",[845],{"type":45,"tag":68,"props":846,"children":847},{"__ignoreMap":149},[848,856,864,872,880,888,896,904,911,919,926,933,940,947,955],{"type":45,"tag":155,"props":849,"children":850},{"class":157,"line":158},[851],{"type":45,"tag":155,"props":852,"children":853},{},[854],{"type":50,"value":855},"\u002F\u002F WRONG — cleanup after abort is dead code\n",{"type":45,"tag":155,"props":857,"children":858},{"class":157,"line":32},[859],{"type":45,"tag":155,"props":860,"children":861},{},[862],{"type":50,"value":863},"#[test, expected_failure(abort_code = my_app::EInsufficientBalance)]\n",{"type":45,"tag":155,"props":865,"children":866},{"class":157,"line":175},[867],{"type":45,"tag":155,"props":868,"children":869},{},[870],{"type":50,"value":871},"fun withdraw_more_than_balance_aborts() {\n",{"type":45,"tag":155,"props":873,"children":874},{"class":157,"line":185},[875],{"type":45,"tag":155,"props":876,"children":877},{},[878],{"type":50,"value":879},"    let mut scenario = test_scenario::begin(@0xA);\n",{"type":45,"tag":155,"props":881,"children":882},{"class":157,"line":194},[883],{"type":45,"tag":155,"props":884,"children":885},{},[886],{"type":50,"value":887},"    my_app::withdraw(1000, scenario.ctx());\n",{"type":45,"tag":155,"props":889,"children":890},{"class":157,"line":203},[891],{"type":45,"tag":155,"props":892,"children":893},{},[894],{"type":50,"value":895},"    scenario.end(); \u002F\u002F never reached\n",{"type":45,"tag":155,"props":897,"children":898},{"class":157,"line":211},[899],{"type":45,"tag":155,"props":900,"children":901},{},[902],{"type":50,"value":903},"}\n",{"type":45,"tag":155,"props":905,"children":906},{"class":157,"line":219},[907],{"type":45,"tag":155,"props":908,"children":909},{"emptyLinePlaceholder":179},[910],{"type":50,"value":182},{"type":45,"tag":155,"props":912,"children":913},{"class":157,"line":28},[914],{"type":45,"tag":155,"props":915,"children":916},{},[917],{"type":50,"value":918},"\u002F\u002F CORRECT — let it abort naturally\n",{"type":45,"tag":155,"props":920,"children":921},{"class":157,"line":235},[922],{"type":45,"tag":155,"props":923,"children":924},{},[925],{"type":50,"value":863},{"type":45,"tag":155,"props":927,"children":928},{"class":157,"line":244},[929],{"type":45,"tag":155,"props":930,"children":931},{},[932],{"type":50,"value":871},{"type":45,"tag":155,"props":934,"children":935},{"class":157,"line":252},[936],{"type":45,"tag":155,"props":937,"children":938},{},[939],{"type":50,"value":879},{"type":45,"tag":155,"props":941,"children":942},{"class":157,"line":260},[943],{"type":45,"tag":155,"props":944,"children":945},{},[946],{"type":50,"value":887},{"type":45,"tag":155,"props":948,"children":949},{"class":157,"line":268},[950],{"type":45,"tag":155,"props":951,"children":952},{},[953],{"type":50,"value":954},"    \u002F\u002F no cleanup needed — test aborts above\n",{"type":45,"tag":155,"props":956,"children":957},{"class":157,"line":277},[958],{"type":45,"tag":155,"props":959,"children":960},{},[961],{"type":50,"value":903},{"type":45,"tag":77,"props":963,"children":965},{"id":964},"use-tx_contextdummy-for-simple-tests",[966,967,973],{"type":50,"value":305},{"type":45,"tag":68,"props":968,"children":970},{"className":969},[],[971],{"type":50,"value":972},"tx_context::dummy()",{"type":50,"value":974}," for Simple Tests",{"type":45,"tag":56,"props":976,"children":977},{},[978,980,986,988,993,995,1001,1003,1008],{"type":50,"value":979},"If a test only needs a ",{"type":45,"tag":68,"props":981,"children":983},{"className":982},[],[984],{"type":50,"value":985},"TxContext",{"type":50,"value":987}," and doesn't need multi-transaction simulation, use ",{"type":45,"tag":68,"props":989,"children":991},{"className":990},[],[992],{"type":50,"value":972},{"type":50,"value":994}," instead of a full ",{"type":45,"tag":68,"props":996,"children":998},{"className":997},[],[999],{"type":50,"value":1000},"test_scenario",{"type":50,"value":1002},". Reserve ",{"type":45,"tag":68,"props":1004,"children":1006},{"className":1005},[],[1007],{"type":50,"value":1000},{"type":50,"value":1009}," for tests that actually need to simulate multiple transactions, shared objects, or transfers between addresses.",{"type":45,"tag":144,"props":1011,"children":1013},{"className":146,"code":1012,"language":148,"meta":149,"style":149},"\u002F\u002F WRONG — unnecessary overhead for a simple test\n#[test]\nfun mint_returns_correct_value() {\n    let mut scenario = test_scenario::begin(@0xA);\n    let item = app::create_item(100, scenario.ctx());\n    assert_eq!(item.value(), 100);\n    std::unit_test::destroy(item);\n    scenario.end();\n}\n\n\u002F\u002F CORRECT — dummy context is sufficient\n#[test]\nfun mint_returns_correct_value() {\n    let ctx = &mut tx_context::dummy();\n    let item = app::create_item(100, ctx);\n    assert_eq!(item.value(), 100);\n    std::unit_test::destroy(item);\n}\n",[1014],{"type":45,"tag":68,"props":1015,"children":1016},{"__ignoreMap":149},[1017,1025,1032,1040,1047,1055,1063,1071,1079,1086,1093,1101,1108,1115,1123,1131,1138,1145],{"type":45,"tag":155,"props":1018,"children":1019},{"class":157,"line":158},[1020],{"type":45,"tag":155,"props":1021,"children":1022},{},[1023],{"type":50,"value":1024},"\u002F\u002F WRONG — unnecessary overhead for a simple test\n",{"type":45,"tag":155,"props":1026,"children":1027},{"class":157,"line":32},[1028],{"type":45,"tag":155,"props":1029,"children":1030},{},[1031],{"type":50,"value":191},{"type":45,"tag":155,"props":1033,"children":1034},{"class":157,"line":175},[1035],{"type":45,"tag":155,"props":1036,"children":1037},{},[1038],{"type":50,"value":1039},"fun mint_returns_correct_value() {\n",{"type":45,"tag":155,"props":1041,"children":1042},{"class":157,"line":185},[1043],{"type":45,"tag":155,"props":1044,"children":1045},{},[1046],{"type":50,"value":879},{"type":45,"tag":155,"props":1048,"children":1049},{"class":157,"line":194},[1050],{"type":45,"tag":155,"props":1051,"children":1052},{},[1053],{"type":50,"value":1054},"    let item = app::create_item(100, scenario.ctx());\n",{"type":45,"tag":155,"props":1056,"children":1057},{"class":157,"line":203},[1058],{"type":45,"tag":155,"props":1059,"children":1060},{},[1061],{"type":50,"value":1062},"    assert_eq!(item.value(), 100);\n",{"type":45,"tag":155,"props":1064,"children":1065},{"class":157,"line":211},[1066],{"type":45,"tag":155,"props":1067,"children":1068},{},[1069],{"type":50,"value":1070},"    std::unit_test::destroy(item);\n",{"type":45,"tag":155,"props":1072,"children":1073},{"class":157,"line":219},[1074],{"type":45,"tag":155,"props":1075,"children":1076},{},[1077],{"type":50,"value":1078},"    scenario.end();\n",{"type":45,"tag":155,"props":1080,"children":1081},{"class":157,"line":28},[1082],{"type":45,"tag":155,"props":1083,"children":1084},{},[1085],{"type":50,"value":903},{"type":45,"tag":155,"props":1087,"children":1088},{"class":157,"line":235},[1089],{"type":45,"tag":155,"props":1090,"children":1091},{"emptyLinePlaceholder":179},[1092],{"type":50,"value":182},{"type":45,"tag":155,"props":1094,"children":1095},{"class":157,"line":244},[1096],{"type":45,"tag":155,"props":1097,"children":1098},{},[1099],{"type":50,"value":1100},"\u002F\u002F CORRECT — dummy context is sufficient\n",{"type":45,"tag":155,"props":1102,"children":1103},{"class":157,"line":252},[1104],{"type":45,"tag":155,"props":1105,"children":1106},{},[1107],{"type":50,"value":191},{"type":45,"tag":155,"props":1109,"children":1110},{"class":157,"line":260},[1111],{"type":45,"tag":155,"props":1112,"children":1113},{},[1114],{"type":50,"value":1039},{"type":45,"tag":155,"props":1116,"children":1117},{"class":157,"line":268},[1118],{"type":45,"tag":155,"props":1119,"children":1120},{},[1121],{"type":50,"value":1122},"    let ctx = &mut tx_context::dummy();\n",{"type":45,"tag":155,"props":1124,"children":1125},{"class":157,"line":277},[1126],{"type":45,"tag":155,"props":1127,"children":1128},{},[1129],{"type":50,"value":1130},"    let item = app::create_item(100, ctx);\n",{"type":45,"tag":155,"props":1132,"children":1133},{"class":157,"line":285},[1134],{"type":45,"tag":155,"props":1135,"children":1136},{},[1137],{"type":50,"value":1062},{"type":45,"tag":155,"props":1139,"children":1140},{"class":157,"line":293},[1141],{"type":45,"tag":155,"props":1142,"children":1143},{},[1144],{"type":50,"value":1070},{"type":45,"tag":155,"props":1146,"children":1148},{"class":157,"line":1147},18,[1149],{"type":45,"tag":155,"props":1150,"children":1151},{},[1152],{"type":50,"value":903},{"type":45,"tag":56,"props":1154,"children":1155},{},[1156,1168,1170,1176],{"type":45,"tag":60,"props":1157,"children":1158},{},[1159,1161,1166],{"type":50,"value":1160},"When to use ",{"type":45,"tag":68,"props":1162,"children":1164},{"className":1163},[],[1165],{"type":50,"value":1000},{"type":50,"value":1167},":",{"type":50,"value":1169}," shared objects, multi-transaction flows, testing transfers between addresses, testing ",{"type":45,"tag":68,"props":1171,"children":1173},{"className":1172},[],[1174],{"type":50,"value":1175},"init",{"type":50,"value":1177}," functions, epoch\u002Ftime manipulation.",{"type":45,"tag":56,"props":1179,"children":1180},{},[1181,1191],{"type":45,"tag":60,"props":1182,"children":1183},{},[1184,1185,1190],{"type":50,"value":1160},{"type":45,"tag":68,"props":1186,"children":1188},{"className":1187},[],[1189],{"type":50,"value":972},{"type":50,"value":1167},{"type":50,"value":1192}," pure function tests, single-operation tests, anything that just needs a ctx to create objects.",{"type":45,"tag":77,"props":1194,"children":1196},{"id":1195},"test_scenario-for-multi-transaction-and-authorization-tests",[1197,1202],{"type":45,"tag":68,"props":1198,"children":1200},{"className":1199},[],[1201],{"type":50,"value":1000},{"type":50,"value":1203}," for Multi-Transaction and Authorization Tests",{"type":45,"tag":56,"props":1205,"children":1206},{},[1207,1208,1213,1215,1220],{"type":50,"value":305},{"type":45,"tag":68,"props":1209,"children":1211},{"className":1210},[],[1212],{"type":50,"value":1000},{"type":50,"value":1214}," when you need to simulate multiple transactions, different senders, shared objects, or test ",{"type":45,"tag":68,"props":1216,"children":1218},{"className":1217},[],[1219],{"type":50,"value":1175},{"type":50,"value":1221}," functions. The core API:",{"type":45,"tag":1223,"props":1224,"children":1225},"table",{},[1226,1245],{"type":45,"tag":1227,"props":1228,"children":1229},"thead",{},[1230],{"type":45,"tag":1231,"props":1232,"children":1233},"tr",{},[1234,1240],{"type":45,"tag":1235,"props":1236,"children":1237},"th",{},[1238],{"type":50,"value":1239},"Function",{"type":45,"tag":1235,"props":1241,"children":1242},{},[1243],{"type":50,"value":1244},"Purpose",{"type":45,"tag":1246,"props":1247,"children":1248},"tbody",{},[1249,1275,1299,1316,1333,1350,1367,1390],{"type":45,"tag":1231,"props":1250,"children":1251},{},[1252,1262],{"type":45,"tag":1253,"props":1254,"children":1255},"td",{},[1256],{"type":45,"tag":68,"props":1257,"children":1259},{"className":1258},[],[1260],{"type":50,"value":1261},"test_scenario::begin(@addr)",{"type":45,"tag":1253,"props":1263,"children":1264},{},[1265,1267,1273],{"type":50,"value":1266},"Start a scenario with ",{"type":45,"tag":68,"props":1268,"children":1270},{"className":1269},[],[1271],{"type":50,"value":1272},"@addr",{"type":50,"value":1274}," as the first sender",{"type":45,"tag":1231,"props":1276,"children":1277},{},[1278,1287],{"type":45,"tag":1253,"props":1279,"children":1280},{},[1281],{"type":45,"tag":68,"props":1282,"children":1284},{"className":1283},[],[1285],{"type":50,"value":1286},"scenario.next_tx(@addr)",{"type":45,"tag":1253,"props":1288,"children":1289},{},[1290,1292,1297],{"type":50,"value":1291},"Advance to a new transaction with ",{"type":45,"tag":68,"props":1293,"children":1295},{"className":1294},[],[1296],{"type":50,"value":1272},{"type":50,"value":1298}," as sender",{"type":45,"tag":1231,"props":1300,"children":1301},{},[1302,1311],{"type":45,"tag":1253,"props":1303,"children":1304},{},[1305],{"type":45,"tag":68,"props":1306,"children":1308},{"className":1307},[],[1309],{"type":50,"value":1310},"scenario.take_from_sender\u003CT>()",{"type":45,"tag":1253,"props":1312,"children":1313},{},[1314],{"type":50,"value":1315},"Take an owned object sent to the current sender",{"type":45,"tag":1231,"props":1317,"children":1318},{},[1319,1328],{"type":45,"tag":1253,"props":1320,"children":1321},{},[1322],{"type":45,"tag":68,"props":1323,"children":1325},{"className":1324},[],[1326],{"type":50,"value":1327},"scenario.return_to_sender(obj)",{"type":45,"tag":1253,"props":1329,"children":1330},{},[1331],{"type":50,"value":1332},"Return an owned object to the current sender",{"type":45,"tag":1231,"props":1334,"children":1335},{},[1336,1345],{"type":45,"tag":1253,"props":1337,"children":1338},{},[1339],{"type":45,"tag":68,"props":1340,"children":1342},{"className":1341},[],[1343],{"type":50,"value":1344},"scenario.take_shared\u003CT>()",{"type":45,"tag":1253,"props":1346,"children":1347},{},[1348],{"type":50,"value":1349},"Take a shared object by type",{"type":45,"tag":1231,"props":1351,"children":1352},{},[1353,1362],{"type":45,"tag":1253,"props":1354,"children":1355},{},[1356],{"type":45,"tag":68,"props":1357,"children":1359},{"className":1358},[],[1360],{"type":50,"value":1361},"test_scenario::return_shared(obj)",{"type":45,"tag":1253,"props":1363,"children":1364},{},[1365],{"type":50,"value":1366},"Return a shared object",{"type":45,"tag":1231,"props":1368,"children":1369},{},[1370,1379],{"type":45,"tag":1253,"props":1371,"children":1372},{},[1373],{"type":45,"tag":68,"props":1374,"children":1376},{"className":1375},[],[1377],{"type":50,"value":1378},"scenario.has_most_recent_for_sender\u003CT>()",{"type":45,"tag":1253,"props":1380,"children":1381},{},[1382,1384],{"type":50,"value":1383},"Check if sender has an object of type ",{"type":45,"tag":68,"props":1385,"children":1387},{"className":1386},[],[1388],{"type":50,"value":1389},"T",{"type":45,"tag":1231,"props":1391,"children":1392},{},[1393,1402],{"type":45,"tag":1253,"props":1394,"children":1395},{},[1396],{"type":45,"tag":68,"props":1397,"children":1399},{"className":1398},[],[1400],{"type":50,"value":1401},"scenario.end()",{"type":45,"tag":1253,"props":1403,"children":1404},{},[1405],{"type":50,"value":1406},"Finalize the scenario (must be called in non-aborting tests)",{"type":45,"tag":1408,"props":1409,"children":1411},"h3",{"id":1410},"success-test-create-and-verify",[1412],{"type":50,"value":1413},"Success test — create and verify",{"type":45,"tag":144,"props":1415,"children":1417},{"className":146,"code":1416,"language":148,"meta":149,"style":149},"#[test]\nfun owner_can_update_item() {\n    let owner = @0xA;\n    let mut scenario = test_scenario::begin(owner);\n\n    \u002F\u002F Tx 1: create an item (transferred to owner inside create_item)\n    app::create_item(b\"sword\".to_string(), scenario.ctx());\n\n    \u002F\u002F Tx 2: owner takes the item and updates it\n    scenario.next_tx(owner);\n    let mut item = scenario.take_from_sender\u003CItem>();\n    app::set_name(&mut item, b\"great sword\".to_string());\n    assert_eq!(app::name(&item), b\"great sword\".to_string());\n    scenario.return_to_sender(item);\n\n    scenario.end();\n}\n",[1418],{"type":45,"tag":68,"props":1419,"children":1420},{"__ignoreMap":149},[1421,1428,1436,1444,1452,1459,1467,1475,1482,1490,1498,1506,1514,1522,1530,1537,1544],{"type":45,"tag":155,"props":1422,"children":1423},{"class":157,"line":158},[1424],{"type":45,"tag":155,"props":1425,"children":1426},{},[1427],{"type":50,"value":191},{"type":45,"tag":155,"props":1429,"children":1430},{"class":157,"line":32},[1431],{"type":45,"tag":155,"props":1432,"children":1433},{},[1434],{"type":50,"value":1435},"fun owner_can_update_item() {\n",{"type":45,"tag":155,"props":1437,"children":1438},{"class":157,"line":175},[1439],{"type":45,"tag":155,"props":1440,"children":1441},{},[1442],{"type":50,"value":1443},"    let owner = @0xA;\n",{"type":45,"tag":155,"props":1445,"children":1446},{"class":157,"line":185},[1447],{"type":45,"tag":155,"props":1448,"children":1449},{},[1450],{"type":50,"value":1451},"    let mut scenario = test_scenario::begin(owner);\n",{"type":45,"tag":155,"props":1453,"children":1454},{"class":157,"line":194},[1455],{"type":45,"tag":155,"props":1456,"children":1457},{"emptyLinePlaceholder":179},[1458],{"type":50,"value":182},{"type":45,"tag":155,"props":1460,"children":1461},{"class":157,"line":203},[1462],{"type":45,"tag":155,"props":1463,"children":1464},{},[1465],{"type":50,"value":1466},"    \u002F\u002F Tx 1: create an item (transferred to owner inside create_item)\n",{"type":45,"tag":155,"props":1468,"children":1469},{"class":157,"line":211},[1470],{"type":45,"tag":155,"props":1471,"children":1472},{},[1473],{"type":50,"value":1474},"    app::create_item(b\"sword\".to_string(), scenario.ctx());\n",{"type":45,"tag":155,"props":1476,"children":1477},{"class":157,"line":219},[1478],{"type":45,"tag":155,"props":1479,"children":1480},{"emptyLinePlaceholder":179},[1481],{"type":50,"value":182},{"type":45,"tag":155,"props":1483,"children":1484},{"class":157,"line":28},[1485],{"type":45,"tag":155,"props":1486,"children":1487},{},[1488],{"type":50,"value":1489},"    \u002F\u002F Tx 2: owner takes the item and updates it\n",{"type":45,"tag":155,"props":1491,"children":1492},{"class":157,"line":235},[1493],{"type":45,"tag":155,"props":1494,"children":1495},{},[1496],{"type":50,"value":1497},"    scenario.next_tx(owner);\n",{"type":45,"tag":155,"props":1499,"children":1500},{"class":157,"line":244},[1501],{"type":45,"tag":155,"props":1502,"children":1503},{},[1504],{"type":50,"value":1505},"    let mut item = scenario.take_from_sender\u003CItem>();\n",{"type":45,"tag":155,"props":1507,"children":1508},{"class":157,"line":252},[1509],{"type":45,"tag":155,"props":1510,"children":1511},{},[1512],{"type":50,"value":1513},"    app::set_name(&mut item, b\"great sword\".to_string());\n",{"type":45,"tag":155,"props":1515,"children":1516},{"class":157,"line":260},[1517],{"type":45,"tag":155,"props":1518,"children":1519},{},[1520],{"type":50,"value":1521},"    assert_eq!(app::name(&item), b\"great sword\".to_string());\n",{"type":45,"tag":155,"props":1523,"children":1524},{"class":157,"line":268},[1525],{"type":45,"tag":155,"props":1526,"children":1527},{},[1528],{"type":50,"value":1529},"    scenario.return_to_sender(item);\n",{"type":45,"tag":155,"props":1531,"children":1532},{"class":157,"line":277},[1533],{"type":45,"tag":155,"props":1534,"children":1535},{"emptyLinePlaceholder":179},[1536],{"type":50,"value":182},{"type":45,"tag":155,"props":1538,"children":1539},{"class":157,"line":285},[1540],{"type":45,"tag":155,"props":1541,"children":1542},{},[1543],{"type":50,"value":1078},{"type":45,"tag":155,"props":1545,"children":1546},{"class":157,"line":293},[1547],{"type":45,"tag":155,"props":1548,"children":1549},{},[1550],{"type":50,"value":903},{"type":45,"tag":1408,"props":1552,"children":1554},{"id":1553},"unauthorized-caller-test",[1555],{"type":50,"value":1556},"Unauthorized caller test",{"type":45,"tag":144,"props":1558,"children":1560},{"className":146,"code":1559,"language":148,"meta":149,"style":149},"#[test, expected_failure(abort_code = app::ENotOwner, location = app)]\nfun non_owner_cannot_update_item() {\n    let owner = @0xA;\n    let attacker = @0xB;\n    let mut scenario = test_scenario::begin(owner);\n\n    \u002F\u002F Tx 1: owner creates a shared item\n    app::create_shared_item(b\"shield\".to_string(), scenario.ctx());\n\n    \u002F\u002F Tx 2: attacker tries to update it — should abort\n    scenario.next_tx(attacker);\n    let mut item = scenario.take_shared\u003CItem>();\n    app::admin_update(&mut item, b\"hacked\".to_string(), scenario.ctx());\n    \u002F\u002F no cleanup — test aborts above\n}\n",[1561],{"type":45,"tag":68,"props":1562,"children":1563},{"__ignoreMap":149},[1564,1572,1580,1587,1595,1602,1609,1617,1625,1632,1640,1648,1656,1664,1672],{"type":45,"tag":155,"props":1565,"children":1566},{"class":157,"line":158},[1567],{"type":45,"tag":155,"props":1568,"children":1569},{},[1570],{"type":50,"value":1571},"#[test, expected_failure(abort_code = app::ENotOwner, location = app)]\n",{"type":45,"tag":155,"props":1573,"children":1574},{"class":157,"line":32},[1575],{"type":45,"tag":155,"props":1576,"children":1577},{},[1578],{"type":50,"value":1579},"fun non_owner_cannot_update_item() {\n",{"type":45,"tag":155,"props":1581,"children":1582},{"class":157,"line":175},[1583],{"type":45,"tag":155,"props":1584,"children":1585},{},[1586],{"type":50,"value":1443},{"type":45,"tag":155,"props":1588,"children":1589},{"class":157,"line":185},[1590],{"type":45,"tag":155,"props":1591,"children":1592},{},[1593],{"type":50,"value":1594},"    let attacker = @0xB;\n",{"type":45,"tag":155,"props":1596,"children":1597},{"class":157,"line":194},[1598],{"type":45,"tag":155,"props":1599,"children":1600},{},[1601],{"type":50,"value":1451},{"type":45,"tag":155,"props":1603,"children":1604},{"class":157,"line":203},[1605],{"type":45,"tag":155,"props":1606,"children":1607},{"emptyLinePlaceholder":179},[1608],{"type":50,"value":182},{"type":45,"tag":155,"props":1610,"children":1611},{"class":157,"line":211},[1612],{"type":45,"tag":155,"props":1613,"children":1614},{},[1615],{"type":50,"value":1616},"    \u002F\u002F Tx 1: owner creates a shared item\n",{"type":45,"tag":155,"props":1618,"children":1619},{"class":157,"line":219},[1620],{"type":45,"tag":155,"props":1621,"children":1622},{},[1623],{"type":50,"value":1624},"    app::create_shared_item(b\"shield\".to_string(), scenario.ctx());\n",{"type":45,"tag":155,"props":1626,"children":1627},{"class":157,"line":28},[1628],{"type":45,"tag":155,"props":1629,"children":1630},{"emptyLinePlaceholder":179},[1631],{"type":50,"value":182},{"type":45,"tag":155,"props":1633,"children":1634},{"class":157,"line":235},[1635],{"type":45,"tag":155,"props":1636,"children":1637},{},[1638],{"type":50,"value":1639},"    \u002F\u002F Tx 2: attacker tries to update it — should abort\n",{"type":45,"tag":155,"props":1641,"children":1642},{"class":157,"line":244},[1643],{"type":45,"tag":155,"props":1644,"children":1645},{},[1646],{"type":50,"value":1647},"    scenario.next_tx(attacker);\n",{"type":45,"tag":155,"props":1649,"children":1650},{"class":157,"line":252},[1651],{"type":45,"tag":155,"props":1652,"children":1653},{},[1654],{"type":50,"value":1655},"    let mut item = scenario.take_shared\u003CItem>();\n",{"type":45,"tag":155,"props":1657,"children":1658},{"class":157,"line":260},[1659],{"type":45,"tag":155,"props":1660,"children":1661},{},[1662],{"type":50,"value":1663},"    app::admin_update(&mut item, b\"hacked\".to_string(), scenario.ctx());\n",{"type":45,"tag":155,"props":1665,"children":1666},{"class":157,"line":268},[1667],{"type":45,"tag":155,"props":1668,"children":1669},{},[1670],{"type":50,"value":1671},"    \u002F\u002F no cleanup — test aborts above\n",{"type":45,"tag":155,"props":1673,"children":1674},{"class":157,"line":277},[1675],{"type":45,"tag":155,"props":1676,"children":1677},{},[1678],{"type":50,"value":903},{"type":45,"tag":1408,"props":1680,"children":1682},{"id":1681},"shared-object-test",[1683],{"type":50,"value":1684},"Shared object test",{"type":45,"tag":144,"props":1686,"children":1688},{"className":146,"code":1687,"language":148,"meta":149,"style":149},"#[test]\nfun shared_counter_increments() {\n    let mut scenario = test_scenario::begin(@0xA);\n\n    \u002F\u002F Tx 1: create and share\n    app::create_counter(scenario.ctx());\n\n    \u002F\u002F Tx 2: anyone can increment\n    scenario.next_tx(@0xB);\n    let mut counter = scenario.take_shared\u003CCounter>();\n    app::increment(&mut counter);\n    assert_eq!(app::value(&counter), 1);\n    test_scenario::return_shared(counter);\n\n    scenario.end();\n}\n",[1689],{"type":45,"tag":68,"props":1690,"children":1691},{"__ignoreMap":149},[1692,1699,1707,1714,1721,1729,1737,1744,1752,1760,1768,1776,1784,1792,1799,1806],{"type":45,"tag":155,"props":1693,"children":1694},{"class":157,"line":158},[1695],{"type":45,"tag":155,"props":1696,"children":1697},{},[1698],{"type":50,"value":191},{"type":45,"tag":155,"props":1700,"children":1701},{"class":157,"line":32},[1702],{"type":45,"tag":155,"props":1703,"children":1704},{},[1705],{"type":50,"value":1706},"fun shared_counter_increments() {\n",{"type":45,"tag":155,"props":1708,"children":1709},{"class":157,"line":175},[1710],{"type":45,"tag":155,"props":1711,"children":1712},{},[1713],{"type":50,"value":879},{"type":45,"tag":155,"props":1715,"children":1716},{"class":157,"line":185},[1717],{"type":45,"tag":155,"props":1718,"children":1719},{"emptyLinePlaceholder":179},[1720],{"type":50,"value":182},{"type":45,"tag":155,"props":1722,"children":1723},{"class":157,"line":194},[1724],{"type":45,"tag":155,"props":1725,"children":1726},{},[1727],{"type":50,"value":1728},"    \u002F\u002F Tx 1: create and share\n",{"type":45,"tag":155,"props":1730,"children":1731},{"class":157,"line":203},[1732],{"type":45,"tag":155,"props":1733,"children":1734},{},[1735],{"type":50,"value":1736},"    app::create_counter(scenario.ctx());\n",{"type":45,"tag":155,"props":1738,"children":1739},{"class":157,"line":211},[1740],{"type":45,"tag":155,"props":1741,"children":1742},{"emptyLinePlaceholder":179},[1743],{"type":50,"value":182},{"type":45,"tag":155,"props":1745,"children":1746},{"class":157,"line":219},[1747],{"type":45,"tag":155,"props":1748,"children":1749},{},[1750],{"type":50,"value":1751},"    \u002F\u002F Tx 2: anyone can increment\n",{"type":45,"tag":155,"props":1753,"children":1754},{"class":157,"line":28},[1755],{"type":45,"tag":155,"props":1756,"children":1757},{},[1758],{"type":50,"value":1759},"    scenario.next_tx(@0xB);\n",{"type":45,"tag":155,"props":1761,"children":1762},{"class":157,"line":235},[1763],{"type":45,"tag":155,"props":1764,"children":1765},{},[1766],{"type":50,"value":1767},"    let mut counter = scenario.take_shared\u003CCounter>();\n",{"type":45,"tag":155,"props":1769,"children":1770},{"class":157,"line":244},[1771],{"type":45,"tag":155,"props":1772,"children":1773},{},[1774],{"type":50,"value":1775},"    app::increment(&mut counter);\n",{"type":45,"tag":155,"props":1777,"children":1778},{"class":157,"line":252},[1779],{"type":45,"tag":155,"props":1780,"children":1781},{},[1782],{"type":50,"value":1783},"    assert_eq!(app::value(&counter), 1);\n",{"type":45,"tag":155,"props":1785,"children":1786},{"class":157,"line":260},[1787],{"type":45,"tag":155,"props":1788,"children":1789},{},[1790],{"type":50,"value":1791},"    test_scenario::return_shared(counter);\n",{"type":45,"tag":155,"props":1793,"children":1794},{"class":157,"line":268},[1795],{"type":45,"tag":155,"props":1796,"children":1797},{"emptyLinePlaceholder":179},[1798],{"type":50,"value":182},{"type":45,"tag":155,"props":1800,"children":1801},{"class":157,"line":277},[1802],{"type":45,"tag":155,"props":1803,"children":1804},{},[1805],{"type":50,"value":1078},{"type":45,"tag":155,"props":1807,"children":1808},{"class":157,"line":285},[1809],{"type":45,"tag":155,"props":1810,"children":1811},{},[1812],{"type":50,"value":903},{"type":45,"tag":1408,"props":1814,"children":1816},{"id":1815},"testing-init-functions",[1817,1819,1824],{"type":50,"value":1818},"Testing ",{"type":45,"tag":68,"props":1820,"children":1822},{"className":1821},[],[1823],{"type":50,"value":1175},{"type":50,"value":1825}," functions",{"type":45,"tag":144,"props":1827,"children":1829},{"className":146,"code":1828,"language":148,"meta":149,"style":149},"#[test]\nfun init_creates_admin_cap() {\n    let mut scenario = test_scenario::begin(@0xA);\n\n    \u002F\u002F init is called automatically for the first tx in begin()\n    \u002F\u002F if the module has an init function — but in tests you call it explicitly:\n    app::init_for_testing(scenario.ctx());\n\n    scenario.next_tx(@0xA);\n    assert!(scenario.has_most_recent_for_sender\u003CAdminCap>());\n\n    scenario.end();\n}\n",[1830],{"type":45,"tag":68,"props":1831,"children":1832},{"__ignoreMap":149},[1833,1840,1848,1855,1862,1870,1878,1886,1893,1901,1909,1916,1923],{"type":45,"tag":155,"props":1834,"children":1835},{"class":157,"line":158},[1836],{"type":45,"tag":155,"props":1837,"children":1838},{},[1839],{"type":50,"value":191},{"type":45,"tag":155,"props":1841,"children":1842},{"class":157,"line":32},[1843],{"type":45,"tag":155,"props":1844,"children":1845},{},[1846],{"type":50,"value":1847},"fun init_creates_admin_cap() {\n",{"type":45,"tag":155,"props":1849,"children":1850},{"class":157,"line":175},[1851],{"type":45,"tag":155,"props":1852,"children":1853},{},[1854],{"type":50,"value":879},{"type":45,"tag":155,"props":1856,"children":1857},{"class":157,"line":185},[1858],{"type":45,"tag":155,"props":1859,"children":1860},{"emptyLinePlaceholder":179},[1861],{"type":50,"value":182},{"type":45,"tag":155,"props":1863,"children":1864},{"class":157,"line":194},[1865],{"type":45,"tag":155,"props":1866,"children":1867},{},[1868],{"type":50,"value":1869},"    \u002F\u002F init is called automatically for the first tx in begin()\n",{"type":45,"tag":155,"props":1871,"children":1872},{"class":157,"line":203},[1873],{"type":45,"tag":155,"props":1874,"children":1875},{},[1876],{"type":50,"value":1877},"    \u002F\u002F if the module has an init function — but in tests you call it explicitly:\n",{"type":45,"tag":155,"props":1879,"children":1880},{"class":157,"line":211},[1881],{"type":45,"tag":155,"props":1882,"children":1883},{},[1884],{"type":50,"value":1885},"    app::init_for_testing(scenario.ctx());\n",{"type":45,"tag":155,"props":1887,"children":1888},{"class":157,"line":219},[1889],{"type":45,"tag":155,"props":1890,"children":1891},{"emptyLinePlaceholder":179},[1892],{"type":50,"value":182},{"type":45,"tag":155,"props":1894,"children":1895},{"class":157,"line":28},[1896],{"type":45,"tag":155,"props":1897,"children":1898},{},[1899],{"type":50,"value":1900},"    scenario.next_tx(@0xA);\n",{"type":45,"tag":155,"props":1902,"children":1903},{"class":157,"line":235},[1904],{"type":45,"tag":155,"props":1905,"children":1906},{},[1907],{"type":50,"value":1908},"    assert!(scenario.has_most_recent_for_sender\u003CAdminCap>());\n",{"type":45,"tag":155,"props":1910,"children":1911},{"class":157,"line":244},[1912],{"type":45,"tag":155,"props":1913,"children":1914},{"emptyLinePlaceholder":179},[1915],{"type":50,"value":182},{"type":45,"tag":155,"props":1917,"children":1918},{"class":157,"line":252},[1919],{"type":45,"tag":155,"props":1920,"children":1921},{},[1922],{"type":50,"value":1078},{"type":45,"tag":155,"props":1924,"children":1925},{"class":157,"line":260},[1926],{"type":45,"tag":155,"props":1927,"children":1928},{},[1929],{"type":50,"value":903},{"type":45,"tag":56,"props":1931,"children":1932},{},[1933,1935,1941,1942,1948,1950,1955,1957,1963],{"type":50,"value":1934},"Note: modules typically expose a ",{"type":45,"tag":68,"props":1936,"children":1938},{"className":1937},[],[1939],{"type":50,"value":1940},"init_for_testing",{"type":50,"value":339},{"type":45,"tag":68,"props":1943,"children":1945},{"className":1944},[],[1946],{"type":50,"value":1947},"test_init",{"type":50,"value":1949}," helper since ",{"type":45,"tag":68,"props":1951,"children":1953},{"className":1952},[],[1954],{"type":50,"value":1175},{"type":50,"value":1956}," itself is not directly callable in tests. Use ",{"type":45,"tag":68,"props":1958,"children":1960},{"className":1959},[],[1961],{"type":50,"value":1962},"#[test_only]",{"type":50,"value":1964}," to gate these helpers.",{"type":45,"tag":77,"props":1966,"children":1968},{"id":1967},"use-stdunit_testdestroy-for-cleanup",[1969,1970,1976],{"type":50,"value":305},{"type":45,"tag":68,"props":1971,"children":1973},{"className":1972},[],[1974],{"type":50,"value":1975},"std::unit_test::destroy",{"type":50,"value":1977}," for Cleanup",{"type":45,"tag":56,"props":1979,"children":1980},{},[1981,1982,1987,1989,1995,1997,2002],{"type":50,"value":305},{"type":45,"tag":68,"props":1983,"children":1985},{"className":1984},[],[1986],{"type":50,"value":1975},{"type":50,"value":1988}," to clean up test objects. The old ",{"type":45,"tag":68,"props":1990,"children":1992},{"className":1991},[],[1993],{"type":50,"value":1994},"sui::test_utils::destroy",{"type":50,"value":1996}," is ",{"type":45,"tag":60,"props":1998,"children":1999},{},[2000],{"type":50,"value":2001},"deprecated",{"type":50,"value":2003},".",{"type":45,"tag":144,"props":2005,"children":2007},{"className":146,"code":2006,"language":148,"meta":149,"style":149},"\u002F\u002F WRONG — deprecated\nuse sui::test_utils::destroy;\n\n\u002F\u002F WRONG — custom cleanup functions\nnft.destroy_for_testing();\n\n\u002F\u002F CORRECT — use std::unit_test::destroy\nuse std::unit_test::destroy;\n\ndestroy(nft);\ndestroy(app);\n",[2008],{"type":45,"tag":68,"props":2009,"children":2010},{"__ignoreMap":149},[2011,2019,2027,2034,2042,2050,2057,2065,2073,2080,2088],{"type":45,"tag":155,"props":2012,"children":2013},{"class":157,"line":158},[2014],{"type":45,"tag":155,"props":2015,"children":2016},{},[2017],{"type":50,"value":2018},"\u002F\u002F WRONG — deprecated\n",{"type":45,"tag":155,"props":2020,"children":2021},{"class":157,"line":32},[2022],{"type":45,"tag":155,"props":2023,"children":2024},{},[2025],{"type":50,"value":2026},"use sui::test_utils::destroy;\n",{"type":45,"tag":155,"props":2028,"children":2029},{"class":157,"line":175},[2030],{"type":45,"tag":155,"props":2031,"children":2032},{"emptyLinePlaceholder":179},[2033],{"type":50,"value":182},{"type":45,"tag":155,"props":2035,"children":2036},{"class":157,"line":185},[2037],{"type":45,"tag":155,"props":2038,"children":2039},{},[2040],{"type":50,"value":2041},"\u002F\u002F WRONG — custom cleanup functions\n",{"type":45,"tag":155,"props":2043,"children":2044},{"class":157,"line":194},[2045],{"type":45,"tag":155,"props":2046,"children":2047},{},[2048],{"type":50,"value":2049},"nft.destroy_for_testing();\n",{"type":45,"tag":155,"props":2051,"children":2052},{"class":157,"line":203},[2053],{"type":45,"tag":155,"props":2054,"children":2055},{"emptyLinePlaceholder":179},[2056],{"type":50,"value":182},{"type":45,"tag":155,"props":2058,"children":2059},{"class":157,"line":211},[2060],{"type":45,"tag":155,"props":2061,"children":2062},{},[2063],{"type":50,"value":2064},"\u002F\u002F CORRECT — use std::unit_test::destroy\n",{"type":45,"tag":155,"props":2066,"children":2067},{"class":157,"line":219},[2068],{"type":45,"tag":155,"props":2069,"children":2070},{},[2071],{"type":50,"value":2072},"use std::unit_test::destroy;\n",{"type":45,"tag":155,"props":2074,"children":2075},{"class":157,"line":28},[2076],{"type":45,"tag":155,"props":2077,"children":2078},{"emptyLinePlaceholder":179},[2079],{"type":50,"value":182},{"type":45,"tag":155,"props":2081,"children":2082},{"class":157,"line":235},[2083],{"type":45,"tag":155,"props":2084,"children":2085},{},[2086],{"type":50,"value":2087},"destroy(nft);\n",{"type":45,"tag":155,"props":2089,"children":2090},{"class":157,"line":244},[2091],{"type":45,"tag":155,"props":2092,"children":2093},{},[2094],{"type":50,"value":2095},"destroy(app);\n",{"type":45,"tag":77,"props":2097,"children":2099},{"id":2098},"quick-reference",[2100],{"type":50,"value":2101},"Quick Reference",{"type":45,"tag":1223,"props":2103,"children":2104},{},[2105,2126],{"type":45,"tag":1227,"props":2106,"children":2107},{},[2108],{"type":45,"tag":1231,"props":2109,"children":2110},{},[2111,2116,2121],{"type":45,"tag":1235,"props":2112,"children":2113},{},[2114],{"type":50,"value":2115},"Pattern",{"type":45,"tag":1235,"props":2117,"children":2118},{},[2119],{"type":50,"value":2120},"Correct",{"type":45,"tag":1235,"props":2122,"children":2123},{},[2124],{"type":50,"value":2125},"Common Mistake",{"type":45,"tag":1246,"props":2127,"children":2128},{},[2129,2155,2181,2207,2240,2265,2293],{"type":45,"tag":1231,"props":2130,"children":2131},{},[2132,2137,2146],{"type":45,"tag":1253,"props":2133,"children":2134},{},[2135],{"type":50,"value":2136},"Test function naming",{"type":45,"tag":1253,"props":2138,"children":2139},{},[2140],{"type":45,"tag":68,"props":2141,"children":2143},{"className":2142},[],[2144],{"type":50,"value":2145},"create_pool_succeeds()",{"type":45,"tag":1253,"props":2147,"children":2148},{},[2149],{"type":45,"tag":68,"props":2150,"children":2152},{"className":2151},[],[2153],{"type":50,"value":2154},"test_create_pool()",{"type":45,"tag":1231,"props":2156,"children":2157},{},[2158,2163,2172],{"type":45,"tag":1253,"props":2159,"children":2160},{},[2161],{"type":50,"value":2162},"Equality assertions",{"type":45,"tag":1253,"props":2164,"children":2165},{},[2166],{"type":45,"tag":68,"props":2167,"children":2169},{"className":2168},[],[2170],{"type":50,"value":2171},"assert_eq!(x, 100)",{"type":45,"tag":1253,"props":2173,"children":2174},{},[2175],{"type":45,"tag":68,"props":2176,"children":2178},{"className":2177},[],[2179],{"type":50,"value":2180},"assert!(x == 100, 0)",{"type":45,"tag":1231,"props":2182,"children":2183},{},[2184,2189,2198],{"type":45,"tag":1253,"props":2185,"children":2186},{},[2187],{"type":50,"value":2188},"Boolean assertions",{"type":45,"tag":1253,"props":2190,"children":2191},{},[2192],{"type":45,"tag":68,"props":2193,"children":2195},{"className":2194},[],[2196],{"type":50,"value":2197},"assert!(is_valid)",{"type":45,"tag":1253,"props":2199,"children":2200},{},[2201],{"type":45,"tag":68,"props":2202,"children":2204},{"className":2203},[],[2205],{"type":50,"value":2206},"assert!(is_valid, 0)",{"type":45,"tag":1231,"props":2208,"children":2209},{},[2210,2215,2224],{"type":45,"tag":1253,"props":2211,"children":2212},{},[2213],{"type":50,"value":2214},"Test attributes",{"type":45,"tag":1253,"props":2216,"children":2217},{},[2218],{"type":45,"tag":68,"props":2219,"children":2221},{"className":2220},[],[2222],{"type":50,"value":2223},"#[test, expected_failure(...)]",{"type":45,"tag":1253,"props":2225,"children":2226},{},[2227,2229,2234,2235],{"type":50,"value":2228},"Separate ",{"type":45,"tag":68,"props":2230,"children":2232},{"className":2231},[],[2233],{"type":50,"value":564},{"type":50,"value":102},{"type":45,"tag":68,"props":2236,"children":2238},{"className":2237},[],[2239],{"type":50,"value":571},{"type":45,"tag":1231,"props":2241,"children":2242},{},[2243,2248,2253],{"type":45,"tag":1253,"props":2244,"children":2245},{},[2246],{"type":50,"value":2247},"Expected failure cleanup",{"type":45,"tag":1253,"props":2249,"children":2250},{},[2251],{"type":50,"value":2252},"Let it abort, no cleanup",{"type":45,"tag":1253,"props":2254,"children":2255},{},[2256,2258,2263],{"type":50,"value":2257},"Calling ",{"type":45,"tag":68,"props":2259,"children":2261},{"className":2260},[],[2262],{"type":50,"value":838},{"type":50,"value":2264}," after abort",{"type":45,"tag":1231,"props":2266,"children":2267},{},[2268,2273,2281],{"type":45,"tag":1253,"props":2269,"children":2270},{},[2271],{"type":50,"value":2272},"Simple test context",{"type":45,"tag":1253,"props":2274,"children":2275},{},[2276],{"type":45,"tag":68,"props":2277,"children":2279},{"className":2278},[],[2280],{"type":50,"value":972},{"type":45,"tag":1253,"props":2282,"children":2283},{},[2284,2286,2291],{"type":50,"value":2285},"Full ",{"type":45,"tag":68,"props":2287,"children":2289},{"className":2288},[],[2290],{"type":50,"value":1000},{"type":50,"value":2292}," for simple tests",{"type":45,"tag":1231,"props":2294,"children":2295},{},[2296,2301,2310],{"type":45,"tag":1253,"props":2297,"children":2298},{},[2299],{"type":50,"value":2300},"Object cleanup",{"type":45,"tag":1253,"props":2302,"children":2303},{},[2304],{"type":45,"tag":68,"props":2305,"children":2307},{"className":2306},[],[2308],{"type":50,"value":2309},"test_utils::destroy(obj)",{"type":45,"tag":1253,"props":2311,"children":2312},{},[2313],{"type":45,"tag":68,"props":2314,"children":2316},{"className":2315},[],[2317],{"type":50,"value":2318},"obj.destroy_for_testing()",{"type":45,"tag":2320,"props":2321,"children":2322},"style",{},[2323],{"type":50,"value":2324},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"items":2326,"total":2487},[2327,2343,2354,2364,2379,2397,2409,2422,2443,2455,2466,2474],{"slug":2328,"name":2328,"fn":2329,"description":2330,"org":2331,"tags":2332,"stars":2340,"repoUrl":2341,"updatedAt":2342},"move-bytecode-comprehension","analyze and disassemble Move bytecode","Use when reading or reasoning about compiled Move bytecode or `sui move disassemble` output. Mental model for the binary format, what survives compilation (and what's lost), and how to read disassembly soundly. Trigger on \"what does this package do?\", \"read this .mv module\", \"interpret this disassembly\", or whenever an analysis needs to interpret bytecode faithfully.\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2333,2336,2339],{"name":2334,"slug":2335,"type":15},"Code Analysis","code-analysis",{"name":2337,"slug":2338,"type":15},"Engineering","engineering",{"name":14,"slug":8,"type":15},7724,"https:\u002F\u002Fgithub.com\u002FMystenLabs\u002Fsui","2026-07-16T05:59:32.904886",{"slug":2344,"name":2344,"fn":2345,"description":2346,"org":2347,"tags":2348,"stars":2340,"repoUrl":2341,"updatedAt":2353},"official-sui-skills","access official Sui development resources","Pointer to the official Mysten Labs skills for building on Sui — language fundamentals, object model, PTBs, SDKs, publishing, upgrades, frontend integration, accessing on-chain data. Maintained upstream at github.com\u002FMystenLabs\u002Fskills; pinned to the same ref the audit catalog derives from (see maintenance\u002FUPSTREAMS.md). Trigger on \"build a contract\", \"publish a package\", \"upgrade a module or package\", \"use the TypeScript SDK\", \"write a PTB\", \"set up a Sui client\".\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2349,2352],{"name":2350,"slug":2351,"type":15},"Documentation","documentation",{"name":14,"slug":8,"type":15},"2026-07-16T06:00:59.641382",{"slug":2355,"name":2355,"fn":2356,"description":2357,"org":2358,"tags":2359,"stars":2340,"repoUrl":2341,"updatedAt":2363},"sui-and-move-tools","disassemble Sui Move bytecode","Use to get bytecode for a deployed Sui package and produce a disassembled working view. One GraphQL call fetches every module's raw bytecode bytes; `sui move disassemble` (already on the system, running `sui prompt`) produces `.asm` files for analysis. Trigger on \"fetch this package's bytecode\", \"get me the .mv for package X\", \"disassemble this package\", or \"I need to read a deployed Sui package\".\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2360,2361,2362],{"name":2334,"slug":2335,"type":15},{"name":2337,"slug":2338,"type":15},{"name":14,"slug":8,"type":15},"2026-07-16T06:02:25.3633",{"slug":2365,"name":2365,"fn":2366,"description":2367,"org":2368,"tags":2369,"stars":2340,"repoUrl":2341,"updatedAt":2378},"sui-move-security-review","audit Sui Move smart contracts","Use when auditing, reviewing, or hunting for vulnerabilities in Move code on Sui. Applies equally to source code (.move files) and to disassembly of compiled bytecode (on-chain packages). A checklist of invariants whose VIOLATION causes exploitable bugs: access control & capabilities, struct abilities & type safety, object lifecycle & ownership, shared-object and PTB attack surface, dynamic fields & collections, arithmetic & coins, init\u002FOTW\u002Fpackage upgrades, hot-potato composability, time & on-chain randomness, and test-only code leakage. Trigger on \"audit this Move code\", \"find vulnerabilities in this Sui contract\", \"security review\", \"is this package safe?\", \"I suspect there's a bug in X\", \"something is wrong with this contract\", or when reasoning about whether a Move function can be abused.\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2370,2373,2376,2377],{"name":2371,"slug":2372,"type":15},"Code Review","code-review",{"name":2374,"slug":2375,"type":15},"Security","security",{"name":23,"slug":24,"type":15},{"name":14,"slug":8,"type":15},"2026-07-16T06:02:55.691149",{"slug":2380,"name":2380,"fn":2381,"description":2382,"org":2383,"tags":2384,"stars":2394,"repoUrl":2395,"updatedAt":2396},"memwal","integrate Walrus Memory SDK","Walrus Memory SDK — portable agent memory that works across apps, sessions, and workflows.\n\nUse when users say:\n- \"add memory to my app\"\n- \"portable agent memory\"\n- \"integrate Walrus Memory\"\n- \"AI agent memory\"\n- \"memory across agents\"\n- \"Walrus memory storage\"\n- \"setup Walrus Memory\"\n- \"recall memories\"\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2385,2388,2391],{"name":2386,"slug":2387,"type":15},"Agents","agents",{"name":2389,"slug":2390,"type":15},"Memory","memory",{"name":2392,"slug":2393,"type":15},"SDK","sdk",57,"https:\u002F\u002Fgithub.com\u002FMystenLabs\u002FMemWal","2026-07-16T06:02:39.838395",{"slug":2398,"name":2398,"fn":2399,"description":2400,"org":2401,"tags":2402,"stars":28,"repoUrl":29,"updatedAt":2408},"accessing-data","read data from the Sui network","How to read data from the Sui network. Use when choosing or implementing a data access strategy — queries for on-chain state, indexing pipelines, historical lookups, event subscriptions, cross-chain reads, or off-chain blob storage. Covers the two live Sui APIs (gRPC and GraphQL RPC), the Archival Store, the General-Purpose Indexer, the `sui-indexer-alt` custom indexing framework, and Walrus for off-chain blobs.\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2403,2406,2407],{"name":2404,"slug":2405,"type":15},"Data Analysis","data-analysis",{"name":14,"slug":8,"type":15},{"name":20,"slug":21,"type":15},"2026-08-01T05:44:32.775598",{"slug":2410,"name":2410,"fn":2411,"description":2412,"org":2413,"tags":2414,"stars":28,"repoUrl":29,"updatedAt":2421},"composable-move-functions","design composable Sui Move functions","Use when writing Move functions on Sui, especially public APIs. Applies to function visibility (public vs entry), parameter ordering, and return patterns. Use whenever designing function signatures or deciding whether functions should transfer objects or return them.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2415,2418,2419,2420],{"name":2416,"slug":2417,"type":15},"API Development","api-development",{"name":23,"slug":24,"type":15},{"name":14,"slug":8,"type":15},{"name":20,"slug":21,"type":15},"2026-07-16T06:02:49.198495",{"slug":2423,"name":2423,"fn":2424,"description":2425,"org":2426,"tags":2427,"stars":28,"repoUrl":29,"updatedAt":2442},"frontend-apps","build Sui dApps with dapp-kit","Sui frontend \u002F dApp development with @mysten\u002Fdapp-kit-react (React) and @mysten\u002Fdapp-kit-core (Vue, vanilla JS, Svelte, Web Components, other frameworks). Use when building browser apps that connect Sui wallets, query on-chain state, or submit transactions. Covers wallet connection, network switching, transaction execution, query patterns with TanStack React Query, and the specific pitfalls of browser + wallet + async-indexer environments. Pair with the `sui-sdks` skill for @mysten\u002Fsui Transaction construction patterns and the `ptbs` skill for PTB semantics.\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2428,2431,2434,2435,2438,2441],{"name":2429,"slug":2430,"type":15},"Frontend","frontend",{"name":2432,"slug":2433,"type":15},"React","react",{"name":14,"slug":8,"type":15},{"name":2436,"slug":2437,"type":15},"Svelte","svelte",{"name":2439,"slug":2440,"type":15},"Vue","vue",{"name":20,"slug":21,"type":15},"2026-08-01T05:44:28.958473",{"slug":2444,"name":2444,"fn":2445,"description":2446,"org":2447,"tags":2448,"stars":28,"repoUrl":29,"updatedAt":2454},"generate-sui-agent-config","generate configuration files for Sui projects","Generate a CLAUDE.md or AGENT.md configuration file for Sui projects. Use when setting up a new Sui project, when user mentions \"CLAUDE.md\", \"AGENT.md\", \"agent config\", or when working on a Sui project that does not already have a CLAUDE.md or AGENT.md in the project root.\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2449,2452,2453],{"name":2450,"slug":2451,"type":15},"Configuration","configuration",{"name":2350,"slug":2351,"type":15},{"name":14,"slug":8,"type":15},"2026-07-16T06:00:59.981056",{"slug":2456,"name":2456,"fn":2457,"description":2458,"org":2459,"tags":2460,"stars":28,"repoUrl":29,"updatedAt":2465},"modern-move-syntax","write Move 2024 edition code for Sui","Use when writing Move code on Sui to ensure 2024 edition syntax is used. Applies to method calls, string literals, vector operations, option handling, loops, and struct unpacking. Use whenever writing Move code to avoid legacy function-call syntax patterns.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2461,2462,2463,2464],{"name":2337,"slug":2338,"type":15},{"name":23,"slug":24,"type":15},{"name":14,"slug":8,"type":15},{"name":20,"slug":21,"type":15},"2026-07-16T06:02:43.277596",{"slug":4,"name":4,"fn":5,"description":6,"org":2467,"tags":2468,"stars":28,"repoUrl":29,"updatedAt":30},{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2469,2470,2471,2472,2473],{"name":17,"slug":18,"type":15},{"name":23,"slug":24,"type":15},{"name":14,"slug":8,"type":15},{"name":26,"slug":27,"type":15},{"name":20,"slug":21,"type":15},{"slug":2475,"name":2475,"fn":2476,"description":2477,"org":2478,"tags":2479,"stars":28,"repoUrl":29,"updatedAt":2486},"naming-conventions","apply Sui Move naming conventions","Use when writing or reviewing Move smart contracts on Sui. Applies to naming structs, error constants, regular constants, events, getter functions, capability types, hot potato types, and dynamic field keys. Use whenever creating new types, functions, or constants in Move code.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2480,2483,2484,2485],{"name":2481,"slug":2482,"type":15},"Best Practices","best-practices",{"name":23,"slug":24,"type":15},{"name":14,"slug":8,"type":15},{"name":20,"slug":21,"type":15},"2026-07-16T06:02:48.830052",37,{"items":2489,"total":2540},[2490,2496,2503,2512,2518,2525,2533],{"slug":2398,"name":2398,"fn":2399,"description":2400,"org":2491,"tags":2492,"stars":28,"repoUrl":29,"updatedAt":2408},{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2493,2494,2495],{"name":2404,"slug":2405,"type":15},{"name":14,"slug":8,"type":15},{"name":20,"slug":21,"type":15},{"slug":2410,"name":2410,"fn":2411,"description":2412,"org":2497,"tags":2498,"stars":28,"repoUrl":29,"updatedAt":2421},{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2499,2500,2501,2502],{"name":2416,"slug":2417,"type":15},{"name":23,"slug":24,"type":15},{"name":14,"slug":8,"type":15},{"name":20,"slug":21,"type":15},{"slug":2423,"name":2423,"fn":2424,"description":2425,"org":2504,"tags":2505,"stars":28,"repoUrl":29,"updatedAt":2442},{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2506,2507,2508,2509,2510,2511],{"name":2429,"slug":2430,"type":15},{"name":2432,"slug":2433,"type":15},{"name":14,"slug":8,"type":15},{"name":2436,"slug":2437,"type":15},{"name":2439,"slug":2440,"type":15},{"name":20,"slug":21,"type":15},{"slug":2444,"name":2444,"fn":2445,"description":2446,"org":2513,"tags":2514,"stars":28,"repoUrl":29,"updatedAt":2454},{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2515,2516,2517],{"name":2450,"slug":2451,"type":15},{"name":2350,"slug":2351,"type":15},{"name":14,"slug":8,"type":15},{"slug":2456,"name":2456,"fn":2457,"description":2458,"org":2519,"tags":2520,"stars":28,"repoUrl":29,"updatedAt":2465},{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2521,2522,2523,2524],{"name":2337,"slug":2338,"type":15},{"name":23,"slug":24,"type":15},{"name":14,"slug":8,"type":15},{"name":20,"slug":21,"type":15},{"slug":4,"name":4,"fn":5,"description":6,"org":2526,"tags":2527,"stars":28,"repoUrl":29,"updatedAt":30},{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2528,2529,2530,2531,2532],{"name":17,"slug":18,"type":15},{"name":23,"slug":24,"type":15},{"name":14,"slug":8,"type":15},{"name":26,"slug":27,"type":15},{"name":20,"slug":21,"type":15},{"slug":2475,"name":2475,"fn":2476,"description":2477,"org":2534,"tags":2535,"stars":28,"repoUrl":29,"updatedAt":2486},{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2536,2537,2538,2539],{"name":2481,"slug":2482,"type":15},{"name":23,"slug":24,"type":15},{"name":14,"slug":8,"type":15},{"name":20,"slug":21,"type":15},20]