diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e24916b..7c4d8e5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,31 +6,47 @@ on: - main pull_request: +env: + RUSTFLAGS: -D warnings + RUSTDOCFLAGS: -D warnings + jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions-rust-lang/setup-rust-toolchain@fb51252c7ba57d633bc668f941da052e410add48 # v1.13.0 + - uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0 + - name: Format + run: cargo fmt --all -- --check + - name: Docs + run: cargo doc --lib --no-deps --all-features + - name: Clippy + run: cargo clippy - name: Build run: cargo build --locked --workspace + + test: + needs: build + runs-on: macos-15 # For snapshot testing. + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions-rust-lang/setup-rust-toolchain@fb51252c7ba57d633bc668f941da052e410add48 # v1.13.0 + - uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0 - name: Test run: cargo test --locked - - name: Test json_tree_test for simd_json feature - run: cargo test --locked --features=simd_json --no-default-features --test json_tree_test - - name: Clippy - run: cargo clippy - - name: Format - run: cargo fmt --all -- --check + - name: Test for simd_json feature + run: cargo test --locked --package egui_json_tree --test image_snapshot_tests --features simd_json --no-default-features web-demo: - needs: build + needs: test permissions: contents: write runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions-rust-lang/setup-rust-toolchain@fb51252c7ba57d633bc668f941da052e410add48 # v1.13.0 + - uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0 - uses: jetli/trunk-action@1346cc09eace4beb84e403e199a471346d4684c9 # v0.5.1 with: version: "v0.21.14" diff --git a/.gitignore b/.gitignore index dd771b6..55e39b9 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ .vscode dist .DS_Store +**/tests/snapshots/**/*.diff.png +**/tests/snapshots/**/*.new.png diff --git a/Cargo.lock b/Cargo.lock index f048eae..76728b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -90,8 +90,8 @@ dependencies = [ "accesskit_consumer", "hashbrown 0.15.4", "static_assertions", - "windows", - "windows-core", + "windows 0.61.3", + "windows-core 0.61.2", ] [[package]] @@ -149,7 +149,7 @@ dependencies = [ "log", "ndk", "ndk-context", - "ndk-sys", + "ndk-sys 0.6.0+11769913", "num_enum", "thiserror 1.0.69", ] @@ -160,6 +160,21 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + [[package]] name = "arboard" version = "3.6.0" @@ -198,6 +213,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" +[[package]] +name = "ash" +version = "0.38.0+1.3.281" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" +dependencies = [ + "libloading", +] + [[package]] name = "async-broadcast" version = "0.7.2" @@ -421,6 +445,12 @@ dependencies = [ "serde", ] +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + [[package]] name = "block2" version = "0.5.1" @@ -565,6 +595,16 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "colored" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" +dependencies = [ + "lazy_static", + "windows-sys 0.59.0", +] + [[package]] name = "combine" version = "4.6.7" @@ -643,6 +683,25 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -668,12 +727,26 @@ dependencies = [ "eframe", "egui", "egui_json_tree", + "egui_kittest", "log", "serde_json", "wasm-bindgen-futures", "web-sys", ] +[[package]] +name = "dify" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11217d469eafa3b809ad84651eb9797ccbb440b4a916d5d85cb1b994e89787f6" +dependencies = [ + "anyhow", + "colored", + "getopts", + "image", + "rayon", +] + [[package]] name = "dispatch" version = "0.2.0" @@ -765,6 +838,7 @@ dependencies = [ "objc2-foundation 0.2.2", "parking_lot", "percent-encoding", + "pollster", "profiling", "raw-window-handle", "static_assertions", @@ -772,6 +846,7 @@ dependencies = [ "wasm-bindgen-futures", "web-sys", "web-time", + "wgpu", "winapi", "windows-sys 0.59.0", "winit", @@ -858,10 +933,33 @@ name = "egui_json_tree" version = "0.13.0" dependencies = [ "egui", + "egui_kittest", "serde_json", "simd-json", ] +[[package]] +name = "egui_kittest" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dacd0e777f0557aebde97346a8c534e76607ce1e4f4a6e10a82d95ec5d5bca8" +dependencies = [ + "dify", + "eframe", + "egui", + "egui-wgpu", + "image", + "kittest", + "pollster", + "wgpu", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "emath" version = "0.32.0" @@ -1107,6 +1205,15 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "getopts" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cba6ae63eb948698e300f645f87c70f76630d505f23b8907cf1e193ee85048c1" +dependencies = [ + "unicode-width", +] + [[package]] name = "getrandom" version = "0.2.16" @@ -1221,6 +1328,57 @@ dependencies = [ "gl_generator", ] +[[package]] +name = "gpu-alloc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" +dependencies = [ + "bitflags 2.9.1", + "gpu-alloc-types", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "gpu-allocator" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd" +dependencies = [ + "log", + "presser", + "thiserror 1.0.69", + "windows 0.58.0", +] + +[[package]] +name = "gpu-descriptor" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" +dependencies = [ + "bitflags 2.9.1", + "gpu-descriptor-types", + "hashbrown 0.15.4", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" +dependencies = [ + "bitflags 2.9.1", +] + [[package]] name = "half" version = "2.6.0" @@ -1469,12 +1627,40 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "libloading", + "pkg-config", +] + [[package]] name = "khronos_api" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" +[[package]] +name = "kittest" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c1bfc4cb16136b6f00fb85a281e4b53d026401cf5dff9a427c466bde5891f0b" +dependencies = [ + "accesskit", + "accesskit_consumer", + "parking_lot", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "lexical-core" version = "1.0.5" @@ -1612,6 +1798,15 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + [[package]] name = "memchr" version = "2.7.5" @@ -1636,6 +1831,21 @@ dependencies = [ "autocfg", ] +[[package]] +name = "metal" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f569fb946490b5743ad69813cb19629130ce9374034abe31614a36402d18f99e" +dependencies = [ + "bitflags 2.9.1", + "block", + "core-graphics-types", + "foreign-types", + "log", + "objc", + "paste", +] + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -1665,6 +1875,7 @@ dependencies = [ "num-traits", "once_cell", "rustc-hash 1.1.0", + "spirv", "strum", "thiserror 2.0.12", "unicode-ident", @@ -1679,7 +1890,7 @@ dependencies = [ "bitflags 2.9.1", "jni-sys", "log", - "ndk-sys", + "ndk-sys 0.6.0+11769913", "num_enum", "raw-window-handle", "thiserror 1.0.69", @@ -1691,6 +1902,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" +[[package]] +name = "ndk-sys" +version = "0.5.0+25.2.9519653" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" +dependencies = [ + "jni-sys", +] + [[package]] name = "ndk-sys" version = "0.6.0+11769913" @@ -1751,6 +1971,15 @@ dependencies = [ "syn", ] +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + [[package]] name = "objc-sys" version = "0.3.5" @@ -2037,6 +2266,15 @@ dependencies = [ "libredox", ] +[[package]] +name = "ordered-float" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" +dependencies = [ + "num-traits", +] + [[package]] name = "ordered-stream" version = "0.2.0" @@ -2085,6 +2323,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -2167,6 +2411,12 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "pollster" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" + [[package]] name = "portable-atomic" version = "1.11.1" @@ -2182,6 +2432,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + [[package]] name = "proc-macro-crate" version = "3.3.0" @@ -2240,12 +2496,38 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "range-alloc" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d6831663a5098ea164f89cff59c6284e95f4e3c76ce9848d4529f5ccca9bde" + [[package]] name = "raw-window-handle" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -2526,6 +2808,15 @@ dependencies = [ "serde", ] +[[package]] +name = "spirv" +version = "0.3.0+sdk-1.3.268.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" +dependencies = [ + "bitflags 2.9.1", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -3083,6 +3374,7 @@ dependencies = [ "hashbrown 0.15.4", "js-sys", "log", + "naga", "parking_lot", "portable-atomic", "profiling", @@ -3090,6 +3382,7 @@ dependencies = [ "smallvec", "static_assertions", "wasm-bindgen", + "wasm-bindgen-futures", "web-sys", "wgpu-core", "wgpu-hal", @@ -3120,11 +3413,31 @@ dependencies = [ "rustc-hash 1.1.0", "smallvec", "thiserror 2.0.12", + "wgpu-core-deps-apple", + "wgpu-core-deps-emscripten", "wgpu-core-deps-windows-linux-android", "wgpu-hal", "wgpu-types", ] +[[package]] +name = "wgpu-core-deps-apple" +version = "25.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfd488b3239b6b7b185c3b045c39ca6bf8af34467a4c5de4e0b1a564135d093d" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-emscripten" +version = "25.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f09ad7aceb3818e52539acc679f049d3475775586f3f4e311c30165cf2c00445" +dependencies = [ + "wgpu-hal", +] + [[package]] name = "wgpu-core-deps-windows-linux-android" version = "25.0.0" @@ -3140,17 +3453,45 @@ version = "25.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f968767fe4d3d33747bbd1473ccd55bf0f6451f55d733b5597e67b5deab4ad17" dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bit-set", "bitflags 2.9.1", + "block", + "bytemuck", + "cfg-if", "cfg_aliases", + "core-graphics-types", + "glow", + "glutin_wgl_sys", + "gpu-alloc", + "gpu-allocator", + "gpu-descriptor", + "hashbrown 0.15.4", + "js-sys", + "khronos-egl", + "libc", "libloading", "log", + "metal", "naga", + "ndk-sys 0.5.0+25.2.9519653", + "objc", + "ordered-float", "parking_lot", "portable-atomic", + "profiling", + "range-alloc", "raw-window-handle", "renderdoc-sys", + "smallvec", "thiserror 2.0.12", + "wasm-bindgen", + "web-sys", "wgpu-types", + "windows 0.58.0", + "windows-core 0.58.0", ] [[package]] @@ -3198,6 +3539,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core 0.58.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows" version = "0.61.3" @@ -3205,7 +3556,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ "windows-collections", - "windows-core", + "windows-core 0.61.2", "windows-future", "windows-link", "windows-numerics", @@ -3217,7 +3568,20 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" dependencies = [ - "windows-core", + "windows-core 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement 0.58.0", + "windows-interface 0.58.0", + "windows-result 0.2.0", + "windows-strings 0.1.0", + "windows-targets 0.52.6", ] [[package]] @@ -3226,11 +3590,11 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ - "windows-implement", - "windows-interface", + "windows-implement 0.60.0", + "windows-interface 0.59.1", "windows-link", - "windows-result", - "windows-strings", + "windows-result 0.3.4", + "windows-strings 0.4.2", ] [[package]] @@ -3239,11 +3603,22 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ - "windows-core", + "windows-core 0.61.2", "windows-link", "windows-threading", ] +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-implement" version = "0.60.0" @@ -3255,6 +3630,17 @@ dependencies = [ "syn", ] +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-interface" version = "0.59.1" @@ -3278,10 +3664,19 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ - "windows-core", + "windows-core 0.61.2", "windows-link", ] +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-result" version = "0.3.4" @@ -3291,6 +3686,16 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result 0.2.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows-strings" version = "0.4.2" diff --git a/Cargo.toml b/Cargo.toml index 8c96803..6a46883 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,4 +12,5 @@ repository = "https://github.com/dmackdev/egui_json_tree" [workspace.dependencies] egui = { version = "0.32", default-features = false } eframe = "0.32" +egui_kittest = "0.32" serde_json = { version = "1" } diff --git a/demo/Cargo.toml b/demo/Cargo.toml index 772d253..c6f0862 100644 --- a/demo/Cargo.toml +++ b/demo/Cargo.toml @@ -19,3 +19,6 @@ serde_json = { workspace = true } log = "0.4" wasm-bindgen-futures = "0.4" web-sys = "0.3.70" # to access the DOM (to hide the loading text) + +[dev-dependencies] +egui_kittest = { workspace = true } diff --git a/demo/src/apps/editor.rs b/demo/src/apps/editor.rs index 08155e5..b671b6c 100644 --- a/demo/src/apps/editor.rs +++ b/demo/src/apps/editor.rs @@ -84,7 +84,9 @@ impl Editor { .is_some_and(|obj| !obj.contains_key(&state.new_key_input)); ui.add_enabled_ui(valid_key, |ui| { - if ui.small_button("✅").clicked() || enter_was_pressed_with_focus { + if ui.small_button("✅").clicked() + || (valid_key && enter_was_pressed_with_focus) + { edit_events.push(EditEvent::SaveObjectKeyEdit); } }); @@ -466,3 +468,241 @@ impl Show for JsonEditorExample { self.editor.apply_events(&mut self.value); } } + +#[cfg(test)] +mod tests { + use egui::{Key, accesskit::Role}; + use egui_kittest::{Harness, kittest::Queryable}; + use serde_json::json; + + use crate::apps::{Show, editor::JsonEditorExample}; + + #[test] + fn add_to_array() { + let mut app = JsonEditorExample::new(json!({ "abc": 123, "def": [5, 6, 7] })); + let mut harness = Harness::new_ui(|ui| { + app.show(ui); + }); + + let array_node = harness.get_by_label_contains("def"); + array_node.click_secondary(); + harness.run(); + + harness + .get_by_role_and_label(Role::Button, "Add to array") + .click(); + harness.run(); + + drop(harness); + + assert_eq!(app.value, json!({ "abc": 123, "def": [5, 6, 7, null] })) + } + + #[test] + fn delete_from_array() { + let mut app = JsonEditorExample::new(json!({ "abc": 123, "def": [5, 6, 7] })); + let mut harness = Harness::new_ui(|ui| { + app.show(ui); + }); + + let array_element_to_delete = harness.get_by_label_contains("6"); + array_element_to_delete.click_secondary(); + harness.run(); + + harness + .get_by_role_and_label(Role::Button, "Delete") + .click(); + harness.run(); + + drop(harness); + + assert_eq!(app.value, json!({ "abc": 123, "def": [5, 7] })) + } + + #[test] + fn edit_array_element() { + let mut app = JsonEditorExample::new(json!({ "abc": 123, "def": [5, 6, 7] })); + let mut harness = Harness::new_ui(|ui| { + app.show(ui); + }); + + let array_element_to_edit = harness.get_by_label_contains("6"); + array_element_to_edit.click_secondary(); + harness.run(); + + harness + .get_by_role_and_label(Role::Button, "Edit value") + .click(); + harness.run(); + + assert!(harness.get_by_role(Role::TextInput).is_focused()); + + // Text input is focussed and we can trigger text input event via any node. + harness.root().type_text("foo"); + harness.run(); + + harness.get_by_role_and_label(Role::Button, "✅").click(); + harness.run(); + assert!(harness.query_by_role(Role::TextInput).is_none()); + + drop(harness); + + assert_eq!(app.value, json!({ "abc": 123, "def": [5, "foo", 7] })) + } + + #[test] + fn add_to_object() { + let mut app = JsonEditorExample::new(json!({ "abc": { "baz": "qux" }, "def": [5, 6, 7] })); + let mut harness = Harness::new_ui(|ui| { + app.show(ui); + }); + + let object_node = harness.get_by_label_contains("abc"); + object_node.click_secondary(); + harness.run(); + + harness + .get_by_role_and_label(Role::Button, "Add to object") + .click(); + harness.run(); + + assert!(harness.get_by_role(Role::TextInput).is_focused()); + + // Text input is focussed and we can trigger text input event via any node. + harness.root().type_text("foo"); + harness.run(); + + harness.get_by_role_and_label(Role::Button, "✅").click(); + harness.run(); + assert!(harness.query_by_role(Role::TextInput).is_none()); + + drop(harness); + + assert_eq!( + app.value, + json!({ "abc": { "baz": "qux", "foo": null }, "def": [5, 6, 7] }) + ) + } + + #[test] + fn edit_object_key() { + let mut app = JsonEditorExample::new(json!({ "abc": { "baz": "qux" }, "def": [5, 6, 7] })); + let mut harness = Harness::new_ui(|ui| { + app.show(ui); + }); + + let object_node = harness.get_by_label_contains("abc"); + object_node.click_secondary(); + harness.run(); + + harness + .get_by_role_and_label(Role::Button, "Edit key") + .click(); + harness.run(); + + assert!(harness.get_by_role(Role::TextInput).is_focused()); + + // Text input is focussed and we can trigger text input event via any node. + harness.root().type_text("foo"); + harness.run(); + + harness.get_by_role_and_label(Role::Button, "✅").click(); + harness.run(); + assert!(harness.query_by_role(Role::TextInput).is_none()); + + drop(harness); + + assert_eq!( + app.value, + json!({ "foo": { "baz": "qux" }, "def": [5, 6, 7] }) + ) + } + + #[test] + fn when_edit_object_key_can_save_with_enter_key() { + let mut app = JsonEditorExample::new(json!({ "abc": { "baz": "qux" }, "def": [5, 6, 7] })); + let mut harness = Harness::new_ui(|ui| { + app.show(ui); + }); + + let object_node = harness.get_by_label_contains("abc"); + object_node.click_secondary(); + harness.run(); + + harness + .get_by_role_and_label(Role::Button, "Edit key") + .click(); + harness.run(); + + assert!(harness.get_by_role(Role::TextInput).is_focused()); + + // Text input is focussed and we can trigger text input event via any node. + harness.root().type_text("foo"); + harness.run(); + + harness.key_press(Key::Enter); + harness.run(); + assert!(harness.query_by_role(Role::TextInput).is_none()); + + drop(harness); + + assert_eq!( + app.value, + json!({ "foo": { "baz": "qux" }, "def": [5, 6, 7] }) + ) + } + + #[test] + fn when_edit_object_key_and_press_cancel_does_not_change_json() { + let original_json = json!({ "abc": { "baz": "qux" }, "def": [5, 6, 7] }); + + let mut app = JsonEditorExample::new(original_json.clone()); + let mut harness = Harness::new_ui(|ui| { + app.show(ui); + }); + + let object_node = harness.get_by_label_contains("abc"); + object_node.click_secondary(); + harness.run(); + + harness + .get_by_role_and_label(Role::Button, "Edit key") + .click(); + harness.run(); + + assert!(harness.get_by_role(Role::TextInput).is_focused()); + + // Text input is focussed and we can trigger text input event via any node. + harness.root().type_text("foo"); + harness.run(); + + harness.get_by_role_and_label(Role::Button, "❌").click(); + harness.run(); + assert!(harness.query_by_role(Role::TextInput).is_none()); + + drop(harness); + + assert_eq!(app.value, original_json) + } + + #[test] + fn delete_from_object() { + let mut app = JsonEditorExample::new(json!({ "abc": { "baz": "qux" }, "def": [5, 6, 7] })); + let mut harness = Harness::new_ui(|ui| { + app.show(ui); + }); + + let element_to_delete = harness.get_by_label_contains("qux"); + element_to_delete.click_secondary(); + harness.run(); + + harness + .get_by_role_and_label(Role::Button, "Delete") + .click(); + harness.run(); + + drop(harness); + + assert_eq!(app.value, json!({ "abc": {}, "def": [5, 6, 7] })) + } +} diff --git a/egui_json_tree/Cargo.toml b/egui_json_tree/Cargo.toml index 3a3bb67..b6a879e 100644 --- a/egui_json_tree/Cargo.toml +++ b/egui_json_tree/Cargo.toml @@ -22,3 +22,7 @@ simd-json = { version = "0.13", optional = true } default = ["serde_json"] serde_json = ["dep:serde_json"] simd_json = ["dep:simd-json"] + +[dev-dependencies] +egui = { workspace = true, default-features = true } # egui_kittest snapshot testing needs default features +egui_kittest = { workspace = true, features = ["wgpu", "snapshot"] } diff --git a/egui_json_tree/src/node.rs b/egui_json_tree/src/node.rs index 989850a..adc5455 100644 --- a/egui_json_tree/src/node.rs +++ b/egui_json_tree/src/node.rs @@ -206,7 +206,15 @@ impl<'a, 'b, T: ToJsonTreeValue> JsonTreeNode<'a, 'b, T> { if let Some(enabled) = style.toggle_buttons_state.enabled() { ui.add_enabled_ui(enabled, |ui| { - state.show_toggle_button(ui, paint_default_icon) + let _response = state.show_toggle_button(ui, paint_default_icon); + #[cfg(test)] + _response.widget_info(|| { + egui::WidgetInfo::labeled( + egui::WidgetType::CollapsingHeader, + ui.is_enabled(), + JsonPointer(path_segments).to_json_pointer_string(), + ) + }); }); } diff --git a/egui_json_tree/src/tree.rs b/egui_json_tree/src/tree.rs index 06f14a6..4c5999f 100644 --- a/egui_json_tree/src/tree.rs +++ b/egui_json_tree/src/tree.rs @@ -109,3 +109,165 @@ impl<'a, T: ToJsonTreeValue> JsonTree<'a, T> { JsonTreeNode::show(self, ui) } } + +#[cfg(test)] +mod tests { + use std::sync::LazyLock; + + use egui::accesskit::Role; + use egui_kittest::Node; + use egui_kittest::kittest::NodeT; + use egui_kittest::{Harness, kittest::Queryable}; + use serde_json::{Value, json}; + + use crate::{DefaultExpand, JsonTree, JsonTreeStyle, ToggleButtonsState}; + + static OBJECT: LazyLock = LazyLock::new(|| { + json!({ + "bar": { + "grep": 21, + "qux": false, + }, + "baz": null, + "foo": [ + 1, + "two" + ] + }) + }); + + #[test] + fn render_object_with_toggle_buttons_visible_disabled() { + let harness = Harness::new_ui(|ui| { + JsonTree::new("id", &*OBJECT) + .default_expand(DefaultExpand::All) + .style( + JsonTreeStyle::new().toggle_buttons_state(ToggleButtonsState::VisibleDisabled), + ) + .show(ui); + }); + + assert_eq!(query_all_collapsing_headers(&harness).count(), 3); + assert!( + query_all_collapsing_headers(&harness).all(|node| node.accesskit_node().is_disabled()) + ) + } + + #[test] + fn render_object_with_toggle_buttons_visible_enabled() { + let harness = Harness::new_ui(|ui| { + JsonTree::new("id", &*OBJECT) + .default_expand(DefaultExpand::All) + .style( + JsonTreeStyle::new().toggle_buttons_state(ToggleButtonsState::VisibleEnabled), + ) + .show(ui); + }); + + assert_eq!(query_all_collapsing_headers(&harness).count(), 3); + assert!( + query_all_collapsing_headers(&harness).all(|node| !node.accesskit_node().is_disabled()) + ) + } + + #[test] + fn render_object_with_toggle_buttons_hidden() { + let harness = Harness::new_ui(|ui| { + JsonTree::new("id", &*OBJECT) + .default_expand(DefaultExpand::All) + .style(JsonTreeStyle::new().toggle_buttons_state(ToggleButtonsState::Hidden)) + .show(ui); + }); + + assert_eq!(query_all_collapsing_headers(&harness).count(), 0); + } + + #[test] + fn render_object_with_interaction_and_manual_reset_expanded() { + let mut harness = Harness::new_ui_state( + |ui, should_reset_expanded| { + let response = JsonTree::new("id", &*OBJECT) + .default_expand(DefaultExpand::None) + .style(JsonTreeStyle::new().abbreviate_root(true)) + .show(ui); + + if *should_reset_expanded { + response.reset_expanded(ui); + } + }, + false, + ); + + assert_eq!(query_all_collapsing_headers(&harness).count(), 1); + assert_eq!(harness.query_all_by_role(Role::Label).count(), 1); + + get_collapsing_header_node(&harness, "").click(); + harness.run(); + assert_eq!(query_all_collapsing_headers(&harness).count(), 3); + assert_eq!(harness.query_all_by_role(Role::Label).count(), 11); + + get_collapsing_header_node(&harness, "/bar").click(); + harness.run(); + assert_eq!(harness.query_all_by_role(Role::Label).count(), 18); + assert!(harness.query_by_label("\"grep\"").is_some()); + assert!(harness.query_by_label("21").is_some()); + assert!(harness.query_by_label("\"qux\"").is_some()); + assert!(harness.query_by_label("false").is_some()); + + *harness.state_mut() = true; + // Resetting expanded manually has a one frame delay, since the reset call happens after the tree renders, hence two runs. + harness.run(); + harness.run(); + assert_eq!(query_all_collapsing_headers(&harness).count(), 1); + assert_eq!(harness.query_all_by_role(Role::Label).count(), 1); + } + + #[test] + fn render_object_with_default_expand_none_and_abbreviated_root() { + let harness = Harness::new_ui(|ui| { + JsonTree::new("id", &*OBJECT) + .default_expand(DefaultExpand::None) + .style(JsonTreeStyle::new().abbreviate_root(true)) + .show(ui); + }); + assert_eq!(query_all_collapsing_headers(&harness).count(), 1); + assert_eq!(harness.get_by_role(Role::Label).value().unwrap(), "{...}"); + } + + #[test] + fn render_array_with_default_expand_none_and_abbreviated_root() { + let harness = Harness::new_ui(|ui| { + JsonTree::new("id", &json!([1, 2, 3])) + .default_expand(DefaultExpand::None) + .style(JsonTreeStyle::new().abbreviate_root(true)) + .show(ui); + }); + assert_eq!(query_all_collapsing_headers(&harness).count(), 1); + assert_eq!(harness.get_by_role(Role::Label).value().unwrap(), "[...]"); + } + + #[test] + fn render_object_with_default_expand_search_results_or_all_when_empty_string_expands_everything() + { + let harness = Harness::new_ui(|ui| { + JsonTree::new("id", &*OBJECT) + .default_expand(DefaultExpand::SearchResultsOrAll("")) + .show(ui); + }); + assert_eq!(query_all_collapsing_headers(&harness).count(), 3); + assert_eq!(harness.query_all_by_role(Role::Label).count(), 25); + } + + fn query_all_collapsing_headers<'a, S>( + harness: &'a Harness<'_, S>, + ) -> impl Iterator> { + harness.query_all_by_role(Role::Button) + } + + fn get_collapsing_header_node<'a, S>( + harness: &'a Harness<'_, S>, + pointer: &'a str, + ) -> Node<'a> { + harness.get_by_role_and_label(Role::Button, pointer) + } +} diff --git a/egui_json_tree/tests/image_snapshot_tests.rs b/egui_json_tree/tests/image_snapshot_tests.rs new file mode 100644 index 0000000..8f8cd7f --- /dev/null +++ b/egui_json_tree/tests/image_snapshot_tests.rs @@ -0,0 +1,169 @@ +use std::sync::LazyLock; + +use egui_json_tree::{DefaultExpand, JsonTree}; +use egui_kittest::Harness; + +#[cfg(feature = "serde_json")] +use serde_json::{Value, json}; + +#[cfg(all(feature = "simd_json", not(feature = "serde_json")))] +use simd_json::{json, owned::Value}; + +// Keep all keys ordered within objects so rendering order is the same for both serde_json and simd_json. +static OBJECT: LazyLock = LazyLock::new(|| { + json!({ + "bar": { + "grep": 21, + "qux": false, + "thud": { + "a/b": [ + 4, + 5, + { + "m~n": "Greetings!" + } + ] + } + }, + "baz": null, + "foo": [ + 1, + 2, + [ + "grep" + ] + ] + }) +}); + +#[test] +fn render_object_with_default_expand_all() { + let mut harness = Harness::new_ui(|ui| { + JsonTree::new("id", &*OBJECT) + .default_expand(DefaultExpand::All) + .show(ui); + }); + harness.fit_contents(); + harness.snapshot("render_object_with_default_expand_all"); +} + +#[test] +fn render_object_with_default_expand_none() { + let mut harness = Harness::new_ui(|ui| { + JsonTree::new("id", &*OBJECT) + .default_expand(DefaultExpand::None) + .show(ui); + }); + harness.fit_contents(); + harness.snapshot("render_object_with_default_expand_none"); +} + +#[test] +fn render_object_search_results() { + // Harness::fit_contents seems to cause the tree to wrap, so set a fixed size here. + let mut harness = Harness::builder().with_size([350., 400.]).build_ui_state( + |ui, default_expand| { + JsonTree::new("id", &*OBJECT) + .default_expand(*default_expand) + .show(ui); + }, + DefaultExpand::SearchResults(""), + ); + + let mut snapshot_errors = vec![]; + + for (idx, search_default_expand) in [ + DefaultExpand::SearchResults(""), + DefaultExpand::SearchResults("g"), + DefaultExpand::SearchResults("gr"), + DefaultExpand::SearchResults("gre"), + DefaultExpand::SearchResults("gree"), + ] + .into_iter() + .enumerate() + { + *harness.state_mut() = search_default_expand; + harness.run(); + if let Err(err) = harness.try_snapshot(format!( + "default_expand_search_results/{}_{:?}", + idx, search_default_expand + )) { + snapshot_errors.push(err); + } + } + + assert!(snapshot_errors.is_empty()); +} + +#[test] +fn render_object_with_changing_default_expand_automatically_resets_expanded() { + // Harness::fit_contents seems to cause the tree to wrap, so set a fixed size here. + let mut harness = Harness::builder().with_size([350., 400.]).build_ui_state( + |ui, default_expand| { + JsonTree::new("id", &*OBJECT) + .default_expand(*default_expand) + .show(ui); + }, + DefaultExpand::None, + ); + + let mut snapshot_errors = vec![]; + + for (idx, default_expand) in [ + DefaultExpand::None, + DefaultExpand::ToLevel(2), + DefaultExpand::SearchResults("gree"), + DefaultExpand::All, + DefaultExpand::SearchResultsOrAll("null"), + ] + .into_iter() + .enumerate() + { + *harness.state_mut() = default_expand; + harness.run(); + if let Err(err) = harness.try_snapshot(format!( + "changing_default_expand/{}_{:?}", + idx, default_expand + )) { + snapshot_errors.push(err); + } + } + + assert!(snapshot_errors.is_empty()); +} + +#[test] +fn render_object_with_default_expand_to_levels() { + // Harness::fit_contents seems to cause the tree to wrap, so set a fixed size here. + let mut harness = Harness::builder().with_size([350., 400.]).build_ui_state( + |ui, level| { + JsonTree::new("id", &*OBJECT) + .default_expand(DefaultExpand::ToLevel(*level)) + .show(ui); + }, + 0, + ); + let mut snapshot_errors = vec![]; + + for level in 0..=4 { + *harness.state_mut() = level; + harness.run(); + if let Err(err) = harness.try_snapshot(format!("default_expand_to_level/{}", level)) { + snapshot_errors.push(err); + } + } + + assert!(snapshot_errors.is_empty()); +} + +#[test] +fn render_object_with_egui_light_theme_should_style_tree_with_light_theme() { + let mut harness = Harness::new_ui(|ui| { + ui.ctx().set_theme(egui::Theme::Light); + JsonTree::new("id", &*OBJECT) + .default_expand(DefaultExpand::All) + .show(ui); + }); + harness.fit_contents(); + harness.snapshot("light_theme"); +} diff --git a/egui_json_tree/tests/json_tree_test.rs b/egui_json_tree/tests/json_tree_test.rs deleted file mode 100644 index b8474dc..0000000 --- a/egui_json_tree/tests/json_tree_test.rs +++ /dev/null @@ -1,602 +0,0 @@ -use std::sync::Arc; - -use egui::{CentralPanel, Context, FontDefinitions, Style, mutex::Mutex}; -use egui_json_tree::{DefaultExpand, JsonTree, JsonTreeStyle, render::RenderContext}; -#[cfg(feature = "serde_json")] -use serde_json::{Value, json}; - -#[cfg(all(feature = "simd_json", not(feature = "serde_json")))] -use simd_json::{json, owned::Value}; - -#[derive(Debug, PartialEq)] -struct ExpectedRender { - value: Value, - display_value: String, - pointer_str: String, -} - -impl<'a, 'b> From> for ExpectedRender { - fn from(ctx: RenderContext<'a, 'b, Value>) -> Self { - match ctx { - RenderContext::Property(ctx) => ExpectedRender { - value: ctx.value.clone(), - display_value: ctx.property.to_string(), - pointer_str: ctx.pointer.to_json_pointer_string(), - }, - RenderContext::BaseValue(ctx) => ExpectedRender { - value: (ctx.value.clone()), - display_value: ctx.display_value.to_string(), - pointer_str: ctx.pointer.to_json_pointer_string(), - }, - RenderContext::ExpandableDelimiter(ctx) => ExpectedRender { - value: ctx.value.clone(), - display_value: ctx.delimiter.as_ref().to_string(), - pointer_str: ctx.pointer.to_json_pointer_string(), - }, - } - } -} - -#[test] -fn json_tree_render_string() { - let value = json!("Hello World!"); - - let actual: Arc>> = Arc::new(Mutex::new(vec![])); - - egui::__run_test_ui(|ui| { - JsonTree::new("id", &value) - .on_render(|_, render_ctx| { - actual.lock().push(render_ctx.into()); - }) - .show(ui); - }); - - let expected = vec![ExpectedRender { - value: json!("Hello World!"), - display_value: "Hello World!".to_string(), - pointer_str: "".to_string(), - }]; - - assert_eq!(actual.lock().as_slice(), expected); -} - -#[test] -fn json_tree_default_expand_none() { - let value = json!({ - "foo": { - "bar": { - "fizz": true - } - } - }); - - let actual: Arc>> = Arc::new(Mutex::new(vec![])); - - egui::__run_test_ui(|ui| { - JsonTree::new("id", &value) - .default_expand(DefaultExpand::None) - .on_render(|_, render_ctx| { - actual.lock().push(render_ctx.into()); - }) - .show(ui); - }); - - let expected = vec![ - ExpectedRender { - value: value.clone(), - display_value: "{".to_string(), - pointer_str: "".to_string(), - }, - ExpectedRender { - value: json!({ - "bar": { - "fizz": true - } - }), - display_value: "foo".to_string(), - pointer_str: "/foo".to_string(), - }, - ExpectedRender { - value: json!({ - "bar": { - "fizz": true - } - }), - display_value: "{...}".to_string(), - pointer_str: "/foo".to_string(), - }, - ExpectedRender { - value: value.clone(), - display_value: "}".to_string(), - pointer_str: "".to_string(), - }, - ]; - assert_eq!(actual.lock().as_slice(), expected); -} - -#[test] -fn json_tree_default_expand_all() { - let value = json!({ - "foo": { - "bar": { - "fizz": true - } - } - }); - - let actual: Arc>> = Arc::new(Mutex::new(vec![])); - - egui::__run_test_ui(|ui| { - JsonTree::new("id", &value) - .default_expand(DefaultExpand::All) - .on_render(|_, render_ctx| { - actual.lock().push(render_ctx.into()); - }) - .show(ui); - }); - - let expected = vec![ - ExpectedRender { - value: value.clone(), - display_value: "{".to_string(), - pointer_str: "".to_string(), - }, - ExpectedRender { - value: json!({ - "bar": { - "fizz": true - } - }), - display_value: "foo".to_string(), - pointer_str: "/foo".to_string(), - }, - ExpectedRender { - value: json!({ - "bar": { - "fizz": true - } - }), - display_value: "{".to_string(), - pointer_str: "/foo".to_string(), - }, - ExpectedRender { - value: json!({"fizz": true}), - display_value: "bar".to_string(), - pointer_str: "/foo/bar".to_string(), - }, - ExpectedRender { - value: json!({"fizz": true}), - display_value: "{".to_string(), - pointer_str: "/foo/bar".to_string(), - }, - ExpectedRender { - value: json!(true), - display_value: "fizz".to_string(), - pointer_str: "/foo/bar/fizz".to_string(), - }, - ExpectedRender { - value: json!(true), - display_value: "true".to_string(), - pointer_str: "/foo/bar/fizz".to_string(), - }, - ExpectedRender { - value: json!({"fizz": true}), - display_value: "}".to_string(), - pointer_str: "/foo/bar".to_string(), - }, - ExpectedRender { - value: json!({ - "bar": { - "fizz": true - } - }), - display_value: "}".to_string(), - pointer_str: "/foo".to_string(), - }, - ExpectedRender { - value: value.clone(), - display_value: "}".to_string(), - pointer_str: "".to_string(), - }, - ]; - assert_eq!(actual.lock().as_slice(), expected); -} - -#[test] -fn json_tree_default_expand_to_level_one() { - let value = json!({ - "foo": { - "bar": { - "fizz": true - }, - "buzz": [ - { "qux": 50 } - ] - } - }); - - let actual: Arc>> = Arc::new(Mutex::new(vec![])); - - egui::__run_test_ui(|ui| { - JsonTree::new("id", &value) - .default_expand(DefaultExpand::ToLevel(1)) - .on_render(|_, render_ctx| { - actual.lock().push(render_ctx.into()); - }) - .show(ui); - }); - - // Level 1 would expand the top level object and "foo", so we would - // expect to see the keys "bar" and "buzz", but not "fizz" and "qux". - let expected = vec![ - ExpectedRender { - value: value.clone(), - display_value: "{".to_string(), - pointer_str: "".to_string(), - }, - ExpectedRender { - value: json!({ - "bar": { - "fizz": true - }, - "buzz": [ - { "qux": 50 } - ] - }), - display_value: "foo".to_string(), - pointer_str: "/foo".to_string(), - }, - ExpectedRender { - value: json!({ - "bar": { - "fizz": true - }, - "buzz": [ - { "qux": 50 } - ] - }), - display_value: "{".to_string(), - pointer_str: "/foo".to_string(), - }, - ExpectedRender { - value: json!({"fizz": true}), - display_value: "bar".to_string(), - pointer_str: "/foo/bar".to_string(), - }, - ExpectedRender { - value: json!({"fizz": true}), - display_value: "{...}".to_string(), - pointer_str: "/foo/bar".to_string(), - }, - ExpectedRender { - value: json!([{ "qux": 50 }]), - display_value: "buzz".to_string(), - pointer_str: "/foo/buzz".to_string(), - }, - ExpectedRender { - value: json!([{ "qux": 50 }]), - display_value: "[...]".to_string(), - pointer_str: "/foo/buzz".to_string(), - }, - ExpectedRender { - value: json!({ - "bar": { - "fizz": true - }, - "buzz": [ - { "qux": 50 } - ] - }), - display_value: "}".to_string(), - pointer_str: "/foo".to_string(), - }, - ExpectedRender { - value: value.clone(), - display_value: "}".to_string(), - pointer_str: "".to_string(), - }, - ]; - - assert_eq!(actual.lock().as_slice(), expected); -} - -#[test] -fn json_tree_default_expand_search() { - let value = json!({ - "foo": { - "bar": { - "fizz": true - }, - "baz": { - "qux": "thud" - }, - "buzz": [ - { "grep": 50 } - ] - } - }); - - let actual: Arc>> = Arc::new(Mutex::new(vec![])); - - egui::__run_test_ui(|ui| { - JsonTree::new("id", &value) - .default_expand(DefaultExpand::SearchResults("t")) - .on_render(|_, render_ctx| { - actual.lock().push(render_ctx.into()); - }) - .show(ui); - }); - - let expected = vec![ - ExpectedRender { - value: value.clone(), - display_value: "{".to_string(), - pointer_str: "".to_string(), - }, - ExpectedRender { - value: json!({ - "bar": { - "fizz": true - }, - "baz": { - "qux": "thud" - }, - "buzz": [ - { "grep": 50 } - ] - }), - display_value: "foo".to_string(), - pointer_str: "/foo".to_string(), - }, - ExpectedRender { - value: json!({ - "bar": { - "fizz": true - }, - "baz": { - "qux": "thud" - }, - "buzz": [ - { "grep": 50 } - ] - }), - display_value: "{".to_string(), - pointer_str: "/foo".to_string(), - }, - ExpectedRender { - value: json!({"fizz": true}), - display_value: "bar".to_string(), - pointer_str: "/foo/bar".to_string(), - }, - ExpectedRender { - value: json!({"fizz": true}), - display_value: "{".to_string(), - pointer_str: "/foo/bar".to_string(), - }, - ExpectedRender { - value: json!(true), - display_value: "fizz".to_string(), - pointer_str: "/foo/bar/fizz".to_string(), - }, - ExpectedRender { - value: json!(true), - display_value: "true".to_string(), - pointer_str: "/foo/bar/fizz".to_string(), - }, - ExpectedRender { - value: json!({"fizz": true}), - display_value: "}".to_string(), - pointer_str: "/foo/bar".to_string(), - }, - ExpectedRender { - value: json!({"qux": "thud"}), - display_value: "baz".to_string(), - pointer_str: "/foo/baz".to_string(), - }, - ExpectedRender { - value: json!({"qux": "thud"}), - display_value: "{".to_string(), - pointer_str: "/foo/baz".to_string(), - }, - ExpectedRender { - value: json!("thud"), - display_value: "qux".to_string(), - pointer_str: "/foo/baz/qux".to_string(), - }, - ExpectedRender { - value: json!("thud"), - display_value: "thud".to_string(), - pointer_str: "/foo/baz/qux".to_string(), - }, - ExpectedRender { - value: json!({"qux": "thud"}), - display_value: "}".to_string(), - pointer_str: "/foo/baz".to_string(), - }, - ExpectedRender { - value: json!([{ "grep": 50 }]), - display_value: "buzz".to_string(), - pointer_str: "/foo/buzz".to_string(), - }, - ExpectedRender { - value: json!([{ "grep": 50 }]), - display_value: "[...]".to_string(), - pointer_str: "/foo/buzz".to_string(), - }, - ExpectedRender { - value: json!({ - "bar": { - "fizz": true - }, - "baz": { - "qux": "thud" - }, - "buzz": [ - { "grep": 50 } - ] - }), - display_value: "}".to_string(), - pointer_str: "/foo".to_string(), - }, - ExpectedRender { - value: value.clone(), - display_value: "}".to_string(), - pointer_str: "".to_string(), - }, - ]; - - assert_eq!(actual.lock().as_slice(), expected); -} - -#[test] -fn json_tree_reset_expanded() { - let value = json!({ - "baz": { - "qux": 1 - }, - "buzz": [ - 1, - ] - }); - - // Reusing the same Context so the memory persists between multiple frames. - let ctx = Context::default(); - ctx.set_fonts(FontDefinitions::empty()); - ctx.set_style(Style { - animation_time: 0.0, - ..Default::default() - }); - - let id = "id"; - - let expected_all_expanded = vec![ - ExpectedRender { - value: value.clone(), - display_value: "{".to_string(), - pointer_str: "".to_string(), - }, - ExpectedRender { - value: json!({"qux": 1}), - display_value: "baz".to_string(), - pointer_str: "/baz".to_string(), - }, - ExpectedRender { - value: json!({"qux": 1}), - display_value: "{".to_string(), - pointer_str: "/baz".to_string(), - }, - ExpectedRender { - value: json!(1), - display_value: "qux".to_string(), - pointer_str: "/baz/qux".to_string(), - }, - ExpectedRender { - value: json!(1), - display_value: "1".to_string(), - pointer_str: "/baz/qux".to_string(), - }, - ExpectedRender { - value: json!({"qux": 1}), - display_value: "}".to_string(), - pointer_str: "/baz".to_string(), - }, - ExpectedRender { - value: json!([1]), - display_value: "buzz".to_string(), - pointer_str: "/buzz".to_string(), - }, - ExpectedRender { - value: json!([1]), - display_value: "[".to_string(), - pointer_str: "/buzz".to_string(), - }, - ExpectedRender { - value: json!(1), - display_value: "0".to_string(), - pointer_str: "/buzz/0".to_string(), - }, - ExpectedRender { - value: json!(1), - display_value: "1".to_string(), - pointer_str: "/buzz/0".to_string(), - }, - ExpectedRender { - value: json!([1]), - display_value: "]".to_string(), - pointer_str: "/buzz".to_string(), - }, - ExpectedRender { - value: value.clone(), - display_value: "}".to_string(), - pointer_str: "".to_string(), - }, - ]; - - // First, render and expand everything. - // We call `abbreviate_root` to only show "{...}" when the root object is collapsed. - // We expect everything to be expanded as this is the first render. - let _ = ctx.run(Default::default(), |ctx| { - let mut actual: Vec = vec![]; - - CentralPanel::default().show(ctx, |ui| { - JsonTree::new(id, &value) - .default_expand(DefaultExpand::All) - .auto_reset_expanded(false) - .style(JsonTreeStyle::new().abbreviate_root(true)) - .on_render(|_, render_ctx| { - actual.push(render_ctx.into()); - }) - .show(ui); - }); - - assert_eq!(actual, expected_all_expanded); - }); - - // Next we render the same tree but change the `default_expand` setting. - // Because we already rendered the tree with everything expanded, - // we expect everything to be expanded still. - // Note that we call `reset_expanded` after rendering the tree. - let _ = ctx.run(Default::default(), |ctx| { - let mut actual: Vec = vec![]; - - CentralPanel::default().show(ctx, |ui| { - JsonTree::new(id, &value) - .default_expand(DefaultExpand::None) - .auto_reset_expanded(false) - .style(JsonTreeStyle::new().abbreviate_root(true)) - .on_render(|_, render_ctx| { - actual.push(render_ctx.into()); - }) - .show(ui) - .reset_expanded(ui); - }); - - assert_eq!(actual, expected_all_expanded); - }); - - // Now we render again with the same `default_expand` setting as the last render. - // Because we called `reset_expanded` in the last frame, we now expect this setting to be respected, - // and now nothing should be expanded. - let _ = ctx.run(Default::default(), |ctx| { - let mut actual: Vec = vec![]; - - CentralPanel::default().show(ctx, |ui| { - JsonTree::new(id, &value) - .default_expand(DefaultExpand::None) - .auto_reset_expanded(false) - .style(JsonTreeStyle::new().abbreviate_root(true)) - .on_render(|_, render_ctx| { - actual.push(render_ctx.into()); - }) - .show(ui); - }); - - let expected_nothing_expanded = vec![ExpectedRender { - value: value.clone(), - display_value: "{...}".to_string(), - pointer_str: "".to_string(), - }]; - - assert_eq!(actual, expected_nothing_expanded); - }); -} diff --git a/egui_json_tree/tests/snapshots/changing_default_expand/0_None.png b/egui_json_tree/tests/snapshots/changing_default_expand/0_None.png new file mode 100644 index 0000000..d60bdc7 Binary files /dev/null and b/egui_json_tree/tests/snapshots/changing_default_expand/0_None.png differ diff --git a/egui_json_tree/tests/snapshots/changing_default_expand/1_ToLevel(2).png b/egui_json_tree/tests/snapshots/changing_default_expand/1_ToLevel(2).png new file mode 100644 index 0000000..c0d86c1 Binary files /dev/null and b/egui_json_tree/tests/snapshots/changing_default_expand/1_ToLevel(2).png differ diff --git "a/egui_json_tree/tests/snapshots/changing_default_expand/2_SearchResults(\"gree\").png" "b/egui_json_tree/tests/snapshots/changing_default_expand/2_SearchResults(\"gree\").png" new file mode 100644 index 0000000..274c8cb Binary files /dev/null and "b/egui_json_tree/tests/snapshots/changing_default_expand/2_SearchResults(\"gree\").png" differ diff --git a/egui_json_tree/tests/snapshots/changing_default_expand/3_All.png b/egui_json_tree/tests/snapshots/changing_default_expand/3_All.png new file mode 100644 index 0000000..31cdd8c Binary files /dev/null and b/egui_json_tree/tests/snapshots/changing_default_expand/3_All.png differ diff --git "a/egui_json_tree/tests/snapshots/changing_default_expand/4_SearchResultsOrAll(\"null\").png" "b/egui_json_tree/tests/snapshots/changing_default_expand/4_SearchResultsOrAll(\"null\").png" new file mode 100644 index 0000000..2082aa2 Binary files /dev/null and "b/egui_json_tree/tests/snapshots/changing_default_expand/4_SearchResultsOrAll(\"null\").png" differ diff --git "a/egui_json_tree/tests/snapshots/default_expand_search_results/0_SearchResults(\"\").png" "b/egui_json_tree/tests/snapshots/default_expand_search_results/0_SearchResults(\"\").png" new file mode 100644 index 0000000..d60bdc7 Binary files /dev/null and "b/egui_json_tree/tests/snapshots/default_expand_search_results/0_SearchResults(\"\").png" differ diff --git "a/egui_json_tree/tests/snapshots/default_expand_search_results/1_SearchResults(\"g\").png" "b/egui_json_tree/tests/snapshots/default_expand_search_results/1_SearchResults(\"g\").png" new file mode 100644 index 0000000..cfca115 Binary files /dev/null and "b/egui_json_tree/tests/snapshots/default_expand_search_results/1_SearchResults(\"g\").png" differ diff --git "a/egui_json_tree/tests/snapshots/default_expand_search_results/2_SearchResults(\"gr\").png" "b/egui_json_tree/tests/snapshots/default_expand_search_results/2_SearchResults(\"gr\").png" new file mode 100644 index 0000000..a807512 Binary files /dev/null and "b/egui_json_tree/tests/snapshots/default_expand_search_results/2_SearchResults(\"gr\").png" differ diff --git "a/egui_json_tree/tests/snapshots/default_expand_search_results/3_SearchResults(\"gre\").png" "b/egui_json_tree/tests/snapshots/default_expand_search_results/3_SearchResults(\"gre\").png" new file mode 100644 index 0000000..f69b4a4 Binary files /dev/null and "b/egui_json_tree/tests/snapshots/default_expand_search_results/3_SearchResults(\"gre\").png" differ diff --git "a/egui_json_tree/tests/snapshots/default_expand_search_results/4_SearchResults(\"gree\").png" "b/egui_json_tree/tests/snapshots/default_expand_search_results/4_SearchResults(\"gree\").png" new file mode 100644 index 0000000..274c8cb Binary files /dev/null and "b/egui_json_tree/tests/snapshots/default_expand_search_results/4_SearchResults(\"gree\").png" differ diff --git a/egui_json_tree/tests/snapshots/default_expand_to_level/0.png b/egui_json_tree/tests/snapshots/default_expand_to_level/0.png new file mode 100644 index 0000000..657f6f6 Binary files /dev/null and b/egui_json_tree/tests/snapshots/default_expand_to_level/0.png differ diff --git a/egui_json_tree/tests/snapshots/default_expand_to_level/1.png b/egui_json_tree/tests/snapshots/default_expand_to_level/1.png new file mode 100644 index 0000000..b32b4c0 Binary files /dev/null and b/egui_json_tree/tests/snapshots/default_expand_to_level/1.png differ diff --git a/egui_json_tree/tests/snapshots/default_expand_to_level/2.png b/egui_json_tree/tests/snapshots/default_expand_to_level/2.png new file mode 100644 index 0000000..c0d86c1 Binary files /dev/null and b/egui_json_tree/tests/snapshots/default_expand_to_level/2.png differ diff --git a/egui_json_tree/tests/snapshots/default_expand_to_level/3.png b/egui_json_tree/tests/snapshots/default_expand_to_level/3.png new file mode 100644 index 0000000..b01d7ec Binary files /dev/null and b/egui_json_tree/tests/snapshots/default_expand_to_level/3.png differ diff --git a/egui_json_tree/tests/snapshots/default_expand_to_level/4.png b/egui_json_tree/tests/snapshots/default_expand_to_level/4.png new file mode 100644 index 0000000..31cdd8c Binary files /dev/null and b/egui_json_tree/tests/snapshots/default_expand_to_level/4.png differ diff --git a/egui_json_tree/tests/snapshots/light_theme.png b/egui_json_tree/tests/snapshots/light_theme.png new file mode 100644 index 0000000..ed6495e Binary files /dev/null and b/egui_json_tree/tests/snapshots/light_theme.png differ diff --git a/egui_json_tree/tests/snapshots/render_object_with_default_expand_all.png b/egui_json_tree/tests/snapshots/render_object_with_default_expand_all.png new file mode 100644 index 0000000..89ad965 Binary files /dev/null and b/egui_json_tree/tests/snapshots/render_object_with_default_expand_all.png differ diff --git a/egui_json_tree/tests/snapshots/render_object_with_default_expand_none.png b/egui_json_tree/tests/snapshots/render_object_with_default_expand_none.png new file mode 100644 index 0000000..1103a83 Binary files /dev/null and b/egui_json_tree/tests/snapshots/render_object_with_default_expand_none.png differ diff --git a/justfile b/justfile index bcbd588..9d9337e 100644 --- a/justfile +++ b/justfile @@ -1,6 +1,10 @@ test: cargo test - cargo test --features=simd_json --no-default-features --test json_tree_test + cargo test --package egui_json_tree --test image_snapshot_tests --features simd_json --no-default-features + +update_snapshots: + UPDATE_SNAPSHOTS=1 cargo test --test image_snapshot_tests + cargo test --package egui_json_tree --test image_snapshot_tests --features simd_json --no-default-features demo: cargo run -p demo