From 308cab3e5cd5e8d5a8c37498e725f51ab101a908 Mon Sep 17 00:00:00 2001 From: BlaΕΎ Hrastnik Date: Tue, 26 Oct 2021 18:03:03 +0900 Subject: Integration testing harness --- helix-term/tests/integration.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 helix-term/tests/integration.rs (limited to 'helix-term/tests/integration.rs') diff --git a/helix-term/tests/integration.rs b/helix-term/tests/integration.rs new file mode 100644 index 00000000..1ef618f7 --- /dev/null +++ b/helix-term/tests/integration.rs @@ -0,0 +1,24 @@ +use helix_term::{application::Application, args::Args, config::Config}; +use helix_view::current; + +use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers}; + +#[tokio::test] +async fn it_works() { + let args = Args::default(); + let config = Config::default(); + let mut app = Application::new(args, config).unwrap(); + + let inputs = &['i', 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd']; + + for input in inputs { + // TODO: use input.parse:: + app.handle_terminal_events(Ok(Event::Key(KeyEvent { + code: KeyCode::Char(*input), + modifiers: KeyModifiers::NONE, + }))); + } + + let (_, doc) = current!(app.editor); + assert_eq!(doc.text(), "hello world\n"); +} -- cgit v1.2.3-70-g09d2 From 502d3290fb88d8a871b0824adc7987a98104933d Mon Sep 17 00:00:00 2001 From: Skyler Hawthorne Date: Mon, 17 Jan 2022 19:04:40 -0500 Subject: improve test harness * Use new macro syntax for encoding sequences of keys * Make convenience helpers for common test pattern * Use indoc for inline indented raw strings * Add feature flag for integration testing to disable rendering --- Cargo.lock | 285 ++++++++++++++++++++++++++-------------- helix-core/Cargo.toml | 1 + helix-loader/src/lib.rs | 10 +- helix-term/Cargo.toml | 5 + helix-term/src/application.rs | 20 ++- helix-term/tests/integration.rs | 163 ++++++++++++++++++++--- helix-view/src/editor.rs | 1 + 7 files changed, 359 insertions(+), 126 deletions(-) (limited to 'helix-term/tests/integration.rs') diff --git a/Cargo.lock b/Cargo.lock index ddf26790..39afd141 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.57" +version = "1.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" +checksum = "94a45b455c14666b85fc40a019e8ab9eb75e3a124e05494f5397122bc9eb06e0" [[package]] name = "arc-swap" @@ -25,9 +25,9 @@ checksum = "c5d78ce20460b82d3fa150275ed9d55e21064fc7951177baacf86a145c4a4b1f" [[package]] name = "autocfg" -version = "1.1.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "bitflags" @@ -66,9 +66,9 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" [[package]] name = "cc" -version = "1.0.73" +version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" [[package]] name = "cfg-if" @@ -121,9 +121,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.8" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" +checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6" dependencies = [ "cfg-if", "lazy_static", @@ -131,15 +131,15 @@ dependencies = [ [[package]] name = "crossterm" -version = "0.23.2" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2102ea4f781910f8a5b98dd061f4c2023f479ce7bb1236330099ceb5a93cf17" +checksum = "77b75a27dc8d220f1f8521ea69cd55a34d720a200ebb3a624d9aa19193d3b432" dependencies = [ "bitflags", "crossterm_winapi", "futures-core", "libc", - "mio", + "mio 0.7.14", "parking_lot", "signal-hook", "signal-hook-mio", @@ -184,9 +184,9 @@ checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] name = "encoding_rs" -version = "0.8.31" +version = "0.8.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +checksum = "7896dc8abb250ffdda33912550faa54c88ec8b998dec0b2c55ab224921ce11df" dependencies = [ "cfg-if", ] @@ -293,9 +293,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.6" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" +checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c" dependencies = [ "cfg-if", "libc", @@ -452,6 +452,7 @@ dependencies = [ "helix-tui", "helix-view", "ignore", + "indoc", "log", "once_cell", "pulldown-cmark", @@ -460,6 +461,7 @@ dependencies = [ "serde_json", "signal-hook", "signal-hook-tokio", + "smallvec", "tokio", "tokio-stream", "toml", @@ -544,11 +546,20 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "indoc" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a75aeaaef0ce18b58056d306c27b07436fbb34b8816c53094b76dd81803136" +dependencies = [ + "unindent", +] + [[package]] name = "itoa" -version = "1.0.2" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" [[package]] name = "lazy_static" @@ -558,9 +569,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.126" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" +checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b" [[package]] name = "libloading" @@ -574,19 +585,18 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.7" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" dependencies = [ - "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.17" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ "cfg-if", ] @@ -612,9 +622,9 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] name = "memchr" -version = "2.5.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" [[package]] name = "memmap2" @@ -625,6 +635,19 @@ dependencies = [ "libc", ] +[[package]] +name = "mio" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" +dependencies = [ + "libc", + "log", + "miow", + "ntapi", + "winapi", +] + [[package]] name = "mio" version = "0.8.3" @@ -634,14 +657,32 @@ dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", + "windows-sys 0.36.1", +] + +[[package]] +name = "miow" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi", +] + +[[package]] +name = "ntapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +dependencies = [ + "winapi", ] [[package]] name = "num-integer" -version = "0.1.45" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" dependencies = [ "autocfg", "num-traits", @@ -649,9 +690,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" dependencies = [ "autocfg", ] @@ -684,15 +725,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.3" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-sys", + "windows-sys 0.32.0", ] [[package]] @@ -703,9 +744,9 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" [[package]] name = "pin-utils" @@ -715,11 +756,11 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "proc-macro2" -version = "1.0.39" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" dependencies = [ - "unicode-ident", + "unicode-xid", ] [[package]] @@ -744,18 +785,18 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.18" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" dependencies = [ "proc-macro2", ] [[package]] name = "rand" -version = "0.8.5" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" dependencies = [ "rand_core", ] @@ -771,29 +812,28 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.13" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" dependencies = [ "bitflags", ] [[package]] name = "redox_users" -version = "0.4.3" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" dependencies = [ "getrandom", "redox_syscall", - "thiserror", ] [[package]] name = "regex" -version = "1.5.6" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" dependencies = [ "aho-corasick", "memchr", @@ -808,15 +848,15 @@ checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" [[package]] name = "regex-syntax" -version = "0.6.26" +version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" [[package]] name = "retain_mut" -version = "0.1.9" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4389f1d5789befaf6029ebd9f7dac4af7f7e3d61b69d4f30e2ac02b57e7712b0" +checksum = "8c31b5c4033f8fdde8700e4657be2c497e7288f01515be52168c631e2e4d4086" [[package]] name = "ropey" @@ -830,9 +870,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.10" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" [[package]] name = "same-file" @@ -851,18 +891,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "serde" -version = "1.0.137" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.137" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" dependencies = [ "proc-macro2", "quote", @@ -871,9 +911,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.81" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" dependencies = [ "itoa", "ryu", @@ -882,9 +922,9 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.8" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2ad84e47328a31223de7fed7a4f5087f2d6ddfe586cf3ca25b7a165bc0a5aed" +checksum = "98d0516900518c29efa217c298fa1f4e6c6ffc85ae29fd7f4ee48f176e1a9ed5" dependencies = [ "proc-macro2", "quote", @@ -903,12 +943,12 @@ dependencies = [ [[package]] name = "signal-hook-mio" -version = "0.2.3" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +checksum = "29fd5867f1c4f2c5be079aee7a2adf1152ebb04a4bc4d341f504b7dece607ed4" dependencies = [ "libc", - "mio", + "mio 0.7.14", "signal-hook", ] @@ -941,9 +981,9 @@ checksum = "2e24979f63a11545f5f2c60141afe249d4f19f84581ea2138065e400941d83d3" [[package]] name = "slab" -version = "0.4.6" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" +checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" [[package]] name = "slotmap" @@ -995,9 +1035,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "str-buf" -version = "1.0.6" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" +checksum = "d44a3643b4ff9caf57abcee9c2c621d6c03d9135e0d8b589bd9afb5992cb176a" [[package]] name = "str_indices" @@ -1007,13 +1047,13 @@ checksum = "9d9199fa80c817e074620be84374a520062ebac833f358d74b37060ce4a0f2c0" [[package]] name = "syn" -version = "1.0.95" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942" +checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" dependencies = [ "proc-macro2", "quote", - "unicode-ident", + "unicode-xid", ] [[package]] @@ -1029,18 +1069,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.31" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.31" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" dependencies = [ "proc-macro2", "quote", @@ -1067,9 +1107,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" dependencies = [ "tinyvec_macros", ] @@ -1089,7 +1129,7 @@ dependencies = [ "bytes", "libc", "memchr", - "mio", + "mio 0.8.3", "num_cpus", "once_cell", "parking_lot", @@ -1133,9 +1173,9 @@ dependencies = [ [[package]] name = "tree-sitter" -version = "0.20.6" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b3b781640108d29892e8b9684642d2cda5ea05951fd58f0fea1db9edeb9b71" +checksum = "4e34327f8eac545e3f037382471b2b19367725a242bba7bc45edb9efb49fe39a" dependencies = [ "cc", "regex", @@ -1152,9 +1192,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.8" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" [[package]] name = "unicode-general-category" @@ -1162,12 +1202,6 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1218098468b8085b19a2824104c70d976491d247ce194bbd9dc77181150cdfd6" -[[package]] -name = "unicode-ident" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" - [[package]] name = "unicode-linebreak" version = "0.1.2" @@ -1198,6 +1232,18 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "unindent" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f14ee04d9415b52b3aeab06258a3f07093182b88ba0f9b8d203f211a7a7d41c7" + [[package]] name = "url" version = "2.2.2" @@ -1242,9 +1288,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "which" -version = "4.2.5" +version = "4.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae" +checksum = "2a5a7e487e921cf220206864a94a89b6c6905bfc19f1057fa26a4cb360e5c1d2" dependencies = [ "either", "lazy_static", @@ -1282,43 +1328,86 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6" +dependencies = [ + "windows_aarch64_msvc 0.32.0", + "windows_i686_gnu 0.32.0", + "windows_i686_msvc 0.32.0", + "windows_x86_64_gnu 0.32.0", + "windows_x86_64_msvc 0.32.0", +] + [[package]] name = "windows-sys" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" dependencies = [ - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_msvc", + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", ] +[[package]] +name = "windows_aarch64_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" + [[package]] name = "windows_aarch64_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" +[[package]] +name = "windows_i686_gnu" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" + [[package]] name = "windows_i686_gnu" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" +[[package]] +name = "windows_i686_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" + [[package]] name = "windows_i686_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +[[package]] +name = "windows_x86_64_gnu" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" + [[package]] name = "windows_x86_64_gnu" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" +[[package]] +name = "windows_x86_64_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" + [[package]] name = "windows_x86_64_msvc" version = "0.36.1" diff --git a/helix-core/Cargo.toml b/helix-core/Cargo.toml index 6574d144..5eb3b621 100644 --- a/helix-core/Cargo.toml +++ b/helix-core/Cargo.toml @@ -12,6 +12,7 @@ include = ["src/**/*", "README.md"] [features] unicode-lines = ["ropey/unicode_lines"] +integration = [] [dependencies] helix-loader = { version = "0.6", path = "../helix-loader" } diff --git a/helix-loader/src/lib.rs b/helix-loader/src/lib.rs index 767bff7a..595ac7aa 100644 --- a/helix-loader/src/lib.rs +++ b/helix-loader/src/lib.rs @@ -11,17 +11,17 @@ pub fn runtime_dir() -> std::path::PathBuf { return dir.into(); } + if let Ok(dir) = std::env::var("CARGO_MANIFEST_DIR") { + // this is the directory of the crate being run by cargo, we need the workspace path so we take the parent + return std::path::PathBuf::from(dir).parent().unwrap().join(RT_DIR); + } + const RT_DIR: &str = "runtime"; let conf_dir = config_dir().join(RT_DIR); if conf_dir.exists() { return conf_dir; } - if let Ok(dir) = std::env::var("CARGO_MANIFEST_DIR") { - // this is the directory of the crate being run by cargo, we need the workspace path so we take the parent - return std::path::PathBuf::from(dir).parent().unwrap().join(RT_DIR); - } - // fallback to location of the executable being run std::env::current_exe() .ok() diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml index 0f80c416..05f8eed4 100644 --- a/helix-term/Cargo.toml +++ b/helix-term/Cargo.toml @@ -17,6 +17,7 @@ app = true [features] unicode-lines = ["helix-core/unicode-lines"] +integration = [] [[bin]] name = "hx" @@ -73,3 +74,7 @@ signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] } [build-dependencies] helix-loader = { version = "0.6", path = "../helix-loader" } + +[dev-dependencies] +smallvec = "1.8" +indoc = "1.0.3" diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 09b7836f..21595eae 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -187,13 +187,21 @@ impl Application { } fn render(&mut self) { - let mut cx = crate::compositor::Context { - editor: &mut self.editor, - jobs: &mut self.jobs, - scroll: None, - }; + #[cfg(feature = "integration")] + return; + + #[allow(unreachable_code)] + { + let compositor = &mut self.compositor; - // self.compositor.render(&mut cx); + let mut cx = crate::compositor::Context { + editor: &mut self.editor, + jobs: &mut self.jobs, + scroll: None, + }; + + compositor.render(&mut cx); + } } pub async fn event_loop(&mut self) { diff --git a/helix-term/tests/integration.rs b/helix-term/tests/integration.rs index 1ef618f7..31a0d218 100644 --- a/helix-term/tests/integration.rs +++ b/helix-term/tests/integration.rs @@ -1,24 +1,153 @@ -use helix_term::{application::Application, args::Args, config::Config}; -use helix_view::current; +#[cfg(feature = "integration")] +mod integration { + use std::path::PathBuf; -use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers}; + use helix_core::{syntax::AutoPairConfig, Position, Selection, Tendril, Transaction}; + use helix_term::{application::Application, args::Args, config::Config}; + use helix_view::{current, doc, input::parse_macro}; -#[tokio::test] -async fn it_works() { - let args = Args::default(); - let config = Config::default(); - let mut app = Application::new(args, config).unwrap(); + use crossterm::event::{Event, KeyEvent}; + use indoc::indoc; - let inputs = &['i', 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd']; + pub struct TestCase { + pub in_text: String, + pub in_selection: Selection, + pub in_keys: String, + pub out_text: String, + pub out_selection: Selection, + } + + fn test_key_sequence( + app: Option, + test_case: &TestCase, + test_fn: &dyn Fn(&mut Application), + ) -> anyhow::Result<()> { + let mut app = + app.unwrap_or_else(|| Application::new(Args::default(), Config::default()).unwrap()); + + let (view, doc) = current!(app.editor); + + doc.apply( + &Transaction::insert( + doc.text(), + &Selection::single(1, 0), + Tendril::from(&test_case.in_text), + ) + .with_selection(test_case.in_selection.clone()), + view.id, + ); + + let input_keys = parse_macro(&test_case.in_keys)? + .into_iter() + .map(|key_event| Event::Key(KeyEvent::from(key_event))); + + for key in input_keys { + app.handle_terminal_events(Ok(key)); + } + + test_fn(&mut app); + + Ok(()) + } + + /// Use this for very simple test cases where there is one input + /// document, selection, and sequence of key presses, and you just + /// want to verify the resulting document and selection. + fn test_key_sequence_text_result( + args: Args, + config: Config, + test_case: TestCase, + ) -> anyhow::Result<()> { + let app = Application::new(args, config).unwrap(); + + test_key_sequence(Some(app), &test_case, &|app| { + let doc = doc!(app.editor); + assert_eq!(&test_case.out_text, doc.text()); - for input in inputs { - // TODO: use input.parse:: - app.handle_terminal_events(Ok(Event::Key(KeyEvent { - code: KeyCode::Char(*input), - modifiers: KeyModifiers::NONE, - }))); + let mut selections: Vec<_> = doc.selections().values().cloned().collect(); + assert_eq!(1, selections.len()); + + let sel = selections.pop().unwrap(); + assert_eq!(test_case.out_selection, sel); + })?; + + Ok(()) + } + + #[tokio::test] + async fn hello_world() -> anyhow::Result<()> { + test_key_sequence_text_result( + Args::default(), + Config::default(), + TestCase { + in_text: String::new(), + in_selection: Selection::single(0, 1), + // TODO: fix incorrect selection on new doc + in_keys: String::from("ihello worldhl"), + out_text: String::from("hello world\n"), + out_selection: Selection::single(11, 12), + }, + )?; + + Ok(()) } - let (_, doc) = current!(app.editor); - assert_eq!(doc.text(), "hello world\n"); + #[tokio::test] + async fn auto_pairs_basic() -> anyhow::Result<()> { + test_key_sequence_text_result( + Args::default(), + Config::default(), + TestCase { + in_text: String::new(), + in_selection: Selection::single(0, 1), + in_keys: String::from("i(hl"), + out_text: String::from("()\n"), + out_selection: Selection::single(1, 2), + }, + )?; + + test_key_sequence_text_result( + Args::default(), + Config { + editor: helix_view::editor::Config { + auto_pairs: AutoPairConfig::Enable(false), + ..Default::default() + }, + ..Default::default() + }, + TestCase { + in_text: String::new(), + in_selection: Selection::single(0, 1), + in_keys: String::from("i(hl"), + out_text: String::from("(\n"), + out_selection: Selection::single(1, 2), + }, + )?; + + Ok(()) + } + + #[tokio::test] + async fn auto_indent_rs() -> anyhow::Result<()> { + test_key_sequence_text_result( + Args { + files: vec![(PathBuf::from("foo.c"), Position::default())], + ..Default::default() + }, + Config::default(), + TestCase { + in_text: String::from("void foo() {}"), + in_selection: Selection::single(12, 13), + in_keys: String::from("i"), + out_text: String::from(indoc! {r#" + void foo() { + + } + "#}), + out_selection: Selection::single(15, 16), + }, + )?; + + Ok(()) + } } diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index ac19def1..76ac0b51 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -769,6 +769,7 @@ impl Editor { Ok(self.new_file_from_document(action, Document::from(rope, Some(encoding)))) } + // ??? pub fn open(&mut self, path: PathBuf, action: Action) -> Result { let path = helix_core::path::get_canonicalized_path(&path)?; let id = self.document_by_path(&path).map(|doc| doc.id); -- cgit v1.2.3-70-g09d2 From 0f3c10a021bbe79e20bde1f55b87465edeec476d Mon Sep 17 00:00:00 2001 From: Skyler Hawthorne Date: Wed, 16 Mar 2022 23:34:21 -0400 Subject: Fix initial selection of Document in new view When a new View of a Document is created, a default cursor of 0, 0 is created, and it does not get normalized to a single width cursor until at least one movement of the cursor happens. This appears to have no practical negative effect that I could find, but it makes tests difficult to work with, since the initial selection is not what you expect it to be. This changes the initial selection of a new View to be the width of the first grapheme in the text. --- .gitignore | 1 - helix-core/src/auto_pairs.rs | 14 +---- helix-core/src/selection.rs | 10 +++- helix-loader/Cargo.toml | 1 - helix-loader/src/lib.rs | 4 +- helix-term/src/application.rs | 21 ++++++++ helix-term/src/commands.rs | 17 ++++-- helix-term/tests/integration.rs | 112 ++++++++++++++++++++++++++++++---------- helix-view/src/document.rs | 34 +++++++++++- helix-view/src/editor.rs | 24 +++------ 10 files changed, 173 insertions(+), 65 deletions(-) (limited to 'helix-term/tests/integration.rs') diff --git a/.gitignore b/.gitignore index 346d0946..6a6fc782 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ target .direnv helix-term/rustfmt.toml -helix-syntax/languages/ result runtime/grammars diff --git a/helix-core/src/auto_pairs.rs b/helix-core/src/auto_pairs.rs index bcd47356..1131178e 100644 --- a/helix-core/src/auto_pairs.rs +++ b/helix-core/src/auto_pairs.rs @@ -1,9 +1,7 @@ //! When typing the opening character of one of the possible pairs defined below, //! this module provides the functionality to insert the paired closing character. -use crate::{ - graphemes, movement::Direction, Range, Rope, RopeGraphemes, Selection, Tendril, Transaction, -}; +use crate::{graphemes, movement::Direction, Range, Rope, Selection, Tendril, Transaction}; use std::collections::HashMap; use log::debug; @@ -149,14 +147,6 @@ fn prev_char(doc: &Rope, pos: usize) -> Option { doc.get_char(pos - 1) } -fn is_single_grapheme(doc: &Rope, range: &Range) -> bool { - let mut graphemes = RopeGraphemes::new(doc.slice(range.from()..range.to())); - let first = graphemes.next(); - let second = graphemes.next(); - debug!("first: {:#?}, second: {:#?}", first, second); - first.is_some() && second.is_none() -} - /// calculate what the resulting range should be for an auto pair insertion fn get_next_range( doc: &Rope, @@ -189,8 +179,8 @@ fn get_next_range( ); } - let single_grapheme = is_single_grapheme(doc, start_range); let doc_slice = doc.slice(..); + let single_grapheme = start_range.is_single_grapheme(doc_slice); // just skip over graphemes if len_inserted == 0 { diff --git a/helix-core/src/selection.rs b/helix-core/src/selection.rs index 1b2416f5..83bab5e3 100644 --- a/helix-core/src/selection.rs +++ b/helix-core/src/selection.rs @@ -8,7 +8,7 @@ use crate::{ prev_grapheme_boundary, }, movement::Direction, - Assoc, ChangeSet, RopeSlice, + Assoc, ChangeSet, RopeGraphemes, RopeSlice, }; use smallvec::{smallvec, SmallVec}; use std::borrow::Cow; @@ -339,6 +339,14 @@ impl Range { pub fn cursor_line(&self, text: RopeSlice) -> usize { text.char_to_line(self.cursor(text)) } + + /// Returns true if this Range covers a single grapheme in the given text + pub fn is_single_grapheme(&self, doc: RopeSlice) -> bool { + let mut graphemes = RopeGraphemes::new(doc.slice(self.from()..self.to())); + let first = graphemes.next(); + let second = graphemes.next(); + first.is_some() && second.is_none() + } } impl From<(usize, usize)> for Range { diff --git a/helix-loader/Cargo.toml b/helix-loader/Cargo.toml index 20384472..3d8a697c 100644 --- a/helix-loader/Cargo.toml +++ b/helix-loader/Cargo.toml @@ -20,7 +20,6 @@ toml = "0.5" etcetera = "0.4" tree-sitter = "0.20" once_cell = "1.12" - log = "0.4" # TODO: these two should be on !wasm32 only diff --git a/helix-loader/src/lib.rs b/helix-loader/src/lib.rs index 595ac7aa..ff4414b2 100644 --- a/helix-loader/src/lib.rs +++ b/helix-loader/src/lib.rs @@ -13,7 +13,9 @@ pub fn runtime_dir() -> std::path::PathBuf { if let Ok(dir) = std::env::var("CARGO_MANIFEST_DIR") { // this is the directory of the crate being run by cargo, we need the workspace path so we take the parent - return std::path::PathBuf::from(dir).parent().unwrap().join(RT_DIR); + let path = std::path::PathBuf::from(dir).parent().unwrap().join(RT_DIR); + log::debug!("runtime dir: {}", path.to_string_lossy()); + return path; } const RT_DIR: &str = "runtime"; diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 21595eae..146194bf 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -55,8 +55,29 @@ pub struct Application { lsp_progress: LspProgressMap, } +#[cfg(feature = "integration")] +fn setup_integration_logging() { + // Separate file config so we can include year, month and day in file logs + let _ = fern::Dispatch::new() + .format(|out, message, record| { + out.finish(format_args!( + "{} {} [{}] {}", + chrono::Local::now().format("%Y-%m-%dT%H:%M:%S%.3f"), + record.target(), + record.level(), + message + )) + }) + .level(log::LevelFilter::Debug) + .chain(std::io::stdout()) + .apply(); +} + impl Application { pub fn new(args: Args, config: Config) -> Result { + #[cfg(feature = "integration")] + setup_integration_logging(); + use helix_view::editor::Action; let config_dir = helix_loader::config_dir(); diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index c9c8e6a9..6b01cbe3 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2094,10 +2094,17 @@ fn insert_mode(cx: &mut Context) { let (view, doc) = current!(cx.editor); enter_insert_mode(doc); - let selection = doc - .selection(view.id) - .clone() - .transform(|range| Range::new(range.to(), range.from())); + log::trace!( + "entering insert mode with sel: {:?}, text: {:?}", + doc.selection(view.id), + doc.text().to_string() + ); + + let selection = doc.selection(view.id).clone().transform(|range| { + let new_range = Range::new(range.to(), range.from()); + new_range + }); + doc.set_selection(view.id, selection); } @@ -2444,8 +2451,8 @@ fn normal_mode(cx: &mut Context) { graphemes::prev_grapheme_boundary(text, range.to()), ) }); - doc.set_selection(view.id, selection); + doc.set_selection(view.id, selection); doc.restore_cursor = false; } } diff --git a/helix-term/tests/integration.rs b/helix-term/tests/integration.rs index 31a0d218..58883d40 100644 --- a/helix-term/tests/integration.rs +++ b/helix-term/tests/integration.rs @@ -2,9 +2,9 @@ mod integration { use std::path::PathBuf; - use helix_core::{syntax::AutoPairConfig, Position, Selection, Tendril, Transaction}; + use helix_core::{syntax::AutoPairConfig, Position, Selection, Transaction}; use helix_term::{application::Application, args::Args, config::Config}; - use helix_view::{current, doc, input::parse_macro}; + use helix_view::{doc, input::parse_macro}; use crossterm::event::{Event, KeyEvent}; use indoc::indoc; @@ -25,14 +25,14 @@ mod integration { let mut app = app.unwrap_or_else(|| Application::new(Args::default(), Config::default()).unwrap()); - let (view, doc) = current!(app.editor); + let (view, doc) = helix_view::current!(app.editor); + let sel = doc.selection(view.id).clone(); + // replace the initial text with the input text doc.apply( - &Transaction::insert( - doc.text(), - &Selection::single(1, 0), - Tendril::from(&test_case.in_text), - ) + &Transaction::change_by_selection(&doc.text(), &sel, |_| { + (0, doc.text().len_chars(), Some((&test_case.in_text).into())) + }) .with_selection(test_case.in_selection.clone()), view.id, ); @@ -80,12 +80,12 @@ mod integration { Args::default(), Config::default(), TestCase { - in_text: String::new(), + in_text: "\n".into(), in_selection: Selection::single(0, 1), // TODO: fix incorrect selection on new doc - in_keys: String::from("ihello worldhl"), - out_text: String::from("hello world\n"), - out_selection: Selection::single(11, 12), + in_keys: "ihello world".into(), + out_text: "hello world\n".into(), + out_selection: Selection::single(12, 11), }, )?; @@ -93,16 +93,74 @@ mod integration { } #[tokio::test] - async fn auto_pairs_basic() -> anyhow::Result<()> { + async fn insert_mode_cursor_position() -> anyhow::Result<()> { test_key_sequence_text_result( Args::default(), Config::default(), TestCase { in_text: String::new(), + in_selection: Selection::single(0, 0), + in_keys: "i".into(), + out_text: String::new(), + out_selection: Selection::single(0, 0), + }, + )?; + + test_key_sequence_text_result( + Args::default(), + Config::default(), + TestCase { + in_text: "\n".into(), + in_selection: Selection::single(0, 1), + in_keys: "i".into(), + out_text: "\n".into(), + out_selection: Selection::single(1, 0), + }, + )?; + + test_key_sequence_text_result( + Args::default(), + Config::default(), + TestCase { + in_text: "\n".into(), in_selection: Selection::single(0, 1), - in_keys: String::from("i(hl"), - out_text: String::from("()\n"), - out_selection: Selection::single(1, 2), + in_keys: "ii".into(), + out_text: "\n".into(), + out_selection: Selection::single(1, 0), + }, + )?; + + Ok(()) + } + + #[tokio::test] + async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> { + test_key_sequence_text_result( + Args::default(), + Config::default(), + TestCase { + in_text: "\n".into(), + in_selection: Selection::single(0, 1), + in_keys: "i".into(), + out_text: "\n".into(), + out_selection: Selection::single(1, 0), + }, + )?; + + Ok(()) + } + + #[tokio::test] + async fn auto_pairs_basic() -> anyhow::Result<()> { + test_key_sequence_text_result( + Args::default(), + Config::default(), + TestCase { + in_text: "\n".into(), + in_selection: Selection::single(0, 1), + in_keys: "i(".into(), + out_text: "()\n".into(), + out_selection: Selection::single(2, 1), }, )?; @@ -116,11 +174,11 @@ mod integration { ..Default::default() }, TestCase { - in_text: String::new(), + in_text: "\n".into(), in_selection: Selection::single(0, 1), - in_keys: String::from("i(hl"), - out_text: String::from("(\n"), - out_selection: Selection::single(1, 2), + in_keys: "i(".into(), + out_text: "(\n".into(), + out_selection: Selection::single(2, 1), }, )?; @@ -136,15 +194,17 @@ mod integration { }, Config::default(), TestCase { - in_text: String::from("void foo() {}"), - in_selection: Selection::single(12, 13), - in_keys: String::from("i"), - out_text: String::from(indoc! {r#" + in_text: "void foo() {}\n".into(), + in_selection: Selection::single(13, 12), + in_keys: "i".into(), + out_text: indoc! {r#" void foo() { } - "#}), - out_selection: Selection::single(15, 16), + "#} + .trim_start() + .into(), + out_selection: Selection::single(16, 15), }, )?; diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index a2d2af77..00adaa1a 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -1,5 +1,6 @@ use anyhow::{anyhow, bail, Context, Error}; use helix_core::auto_pairs::AutoPairs; +use helix_core::Range; use serde::de::{self, Deserialize, Deserializer}; use serde::Serialize; use std::cell::Cell; @@ -83,7 +84,7 @@ impl Serialize for Mode { pub struct Document { pub(crate) id: DocumentId, text: Rope, - pub(crate) selections: HashMap, + selections: HashMap, path: Option, encoding: &'static encoding::Encoding, @@ -637,6 +638,37 @@ impl Document { .insert(view_id, selection.ensure_invariants(self.text().slice(..))); } + /// Find the origin selection of the text in a document, i.e. where + /// a single cursor would go if it were on the first grapheme. If + /// the text is empty, returns (0, 0). + pub fn origin(&self) -> Range { + if self.text().len_chars() == 0 { + return Range::new(0, 0); + } + + Range::new(0, 1).grapheme_aligned(self.text().slice(..)) + } + + /// Reset the view's selection on this document to the + /// [origin](Document::origin) cursor. + pub fn reset_selection(&mut self, view_id: ViewId) { + let origin = self.origin(); + self.set_selection(view_id, Selection::single(origin.anchor, origin.head)); + } + + /// Initializes a new selection for the given view if it does not + /// already have one. + pub fn ensure_view_init(&mut self, view_id: ViewId) { + if self.selections.get(&view_id).is_none() { + self.reset_selection(view_id); + } + } + + /// Remove a view's selection from this document. + pub fn remove_view(&mut self, view_id: ViewId) { + self.selections.remove(&view_id); + } + /// Apply a [`Transaction`] to the [`Document`] to change its text. fn apply_impl(&mut self, transaction: &Transaction, view_id: ViewId) -> bool { let old_doc = self.text().clone(); diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 76ac0b51..8607c65a 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -32,12 +32,12 @@ use anyhow::{bail, Error}; pub use helix_core::diagnostic::Severity; pub use helix_core::register::Registers; +use helix_core::Position; use helix_core::{ auto_pairs::AutoPairs, syntax::{self, AutoPairConfig}, Change, }; -use helix_core::{Position, Selection}; use helix_dap as dap; use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer}; @@ -645,11 +645,8 @@ impl Editor { view.offset = Position::default(); let doc = self.documents.get_mut(&doc_id).unwrap(); + doc.ensure_view_init(view.id); - // initialize selection for view - doc.selections - .entry(view.id) - .or_insert_with(|| Selection::point(0)); // TODO: reuse align_view let pos = doc .selection(view.id) @@ -719,9 +716,7 @@ impl Editor { Action::Load => { let view_id = view!(self).id; let doc = self.documents.get_mut(&id).unwrap(); - if doc.selections().is_empty() { - doc.set_selection(view_id, Selection::point(0)); - } + doc.ensure_view_init(view_id); return; } Action::HorizontalSplit | Action::VerticalSplit => { @@ -736,7 +731,7 @@ impl Editor { ); // initialize selection for view let doc = self.documents.get_mut(&id).unwrap(); - doc.set_selection(view_id, Selection::point(0)); + doc.ensure_view_init(view_id); } } @@ -769,7 +764,7 @@ impl Editor { Ok(self.new_file_from_document(action, Document::from(rope, Some(encoding)))) } - // ??? + // ??? possible use for integration tests pub fn open(&mut self, path: PathBuf, action: Action) -> Result { let path = helix_core::path::get_canonicalized_path(&path)?; let id = self.document_by_path(&path).map(|doc| doc.id); @@ -791,12 +786,7 @@ impl Editor { pub fn close(&mut self, id: ViewId) { let view = self.tree.get(self.tree.focus); // remove selection - self.documents - .get_mut(&view.doc) - .unwrap() - .selections - .remove(&id); - + self.documents.get_mut(&view.doc).unwrap().remove_view(id); self.tree.remove(id); self._refresh(); } @@ -871,7 +861,7 @@ impl Editor { let view = View::new(doc_id, self.config().gutters.clone()); let view_id = self.tree.insert(view); let doc = self.documents.get_mut(&doc_id).unwrap(); - doc.set_selection(view_id, Selection::point(0)); + doc.ensure_view_init(view_id); } self._refresh(); -- cgit v1.2.3-70-g09d2 From 84bbe6b8f3aa23f3f9f1d8b38844efba6af17b41 Mon Sep 17 00:00:00 2001 From: Skyler Hawthorne Date: Sat, 16 Apr 2022 22:05:45 -0400 Subject: refactor helpers, use new test helpers --- helix-term/tests/integration.rs | 198 ++++++++++++-------------------- helix-term/tests/integration/helpers.rs | 87 ++++++++++++++ 2 files changed, 162 insertions(+), 123 deletions(-) create mode 100644 helix-term/tests/integration/helpers.rs (limited to 'helix-term/tests/integration.rs') diff --git a/helix-term/tests/integration.rs b/helix-term/tests/integration.rs index 58883d40..a32eebf5 100644 --- a/helix-term/tests/integration.rs +++ b/helix-term/tests/integration.rs @@ -1,92 +1,22 @@ #[cfg(feature = "integration")] mod integration { + mod helpers; + use std::path::PathBuf; - use helix_core::{syntax::AutoPairConfig, Position, Selection, Transaction}; - use helix_term::{application::Application, args::Args, config::Config}; - use helix_view::{doc, input::parse_macro}; + use helix_core::{syntax::AutoPairConfig, Position, Selection}; + use helix_term::{args::Args, config::Config}; - use crossterm::event::{Event, KeyEvent}; use indoc::indoc; - pub struct TestCase { - pub in_text: String, - pub in_selection: Selection, - pub in_keys: String, - pub out_text: String, - pub out_selection: Selection, - } - - fn test_key_sequence( - app: Option, - test_case: &TestCase, - test_fn: &dyn Fn(&mut Application), - ) -> anyhow::Result<()> { - let mut app = - app.unwrap_or_else(|| Application::new(Args::default(), Config::default()).unwrap()); - - let (view, doc) = helix_view::current!(app.editor); - let sel = doc.selection(view.id).clone(); - - // replace the initial text with the input text - doc.apply( - &Transaction::change_by_selection(&doc.text(), &sel, |_| { - (0, doc.text().len_chars(), Some((&test_case.in_text).into())) - }) - .with_selection(test_case.in_selection.clone()), - view.id, - ); - - let input_keys = parse_macro(&test_case.in_keys)? - .into_iter() - .map(|key_event| Event::Key(KeyEvent::from(key_event))); - - for key in input_keys { - app.handle_terminal_events(Ok(key)); - } - - test_fn(&mut app); - - Ok(()) - } - - /// Use this for very simple test cases where there is one input - /// document, selection, and sequence of key presses, and you just - /// want to verify the resulting document and selection. - fn test_key_sequence_text_result( - args: Args, - config: Config, - test_case: TestCase, - ) -> anyhow::Result<()> { - let app = Application::new(args, config).unwrap(); - - test_key_sequence(Some(app), &test_case, &|app| { - let doc = doc!(app.editor); - assert_eq!(&test_case.out_text, doc.text()); - - let mut selections: Vec<_> = doc.selections().values().cloned().collect(); - assert_eq!(1, selections.len()); - - let sel = selections.pop().unwrap(); - assert_eq!(test_case.out_selection, sel); - })?; - - Ok(()) - } + use self::helpers::*; #[tokio::test] async fn hello_world() -> anyhow::Result<()> { test_key_sequence_text_result( Args::default(), Config::default(), - TestCase { - in_text: "\n".into(), - in_selection: Selection::single(0, 1), - // TODO: fix incorrect selection on new doc - in_keys: "ihello world".into(), - out_text: "hello world\n".into(), - out_selection: Selection::single(12, 11), - }, + ("#[\n|]#", "ihello world", "hello world#[|\n]#"), )?; Ok(()) @@ -109,42 +39,79 @@ mod integration { test_key_sequence_text_result( Args::default(), Config::default(), - TestCase { - in_text: "\n".into(), - in_selection: Selection::single(0, 1), - in_keys: "i".into(), - out_text: "\n".into(), - out_selection: Selection::single(1, 0), - }, + ("#[\n|]#", "i", "#[|\n]#"), )?; test_key_sequence_text_result( Args::default(), Config::default(), - TestCase { - in_text: "\n".into(), - in_selection: Selection::single(0, 1), - in_keys: "ii".into(), - out_text: "\n".into(), - out_selection: Selection::single(1, 0), - }, + ("#[\n|]#", "i", "#[|\n]#"), + )?; + + test_key_sequence_text_result( + Args::default(), + Config::default(), + ("#[\n|]#", "ii", "#[|\n]#"), )?; Ok(()) } + /// Range direction is preserved when escaping insert mode to normal #[tokio::test] async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> { test_key_sequence_text_result( Args::default(), Config::default(), - TestCase { - in_text: "\n".into(), - in_selection: Selection::single(0, 1), - in_keys: "i".into(), - out_text: "\n".into(), - out_selection: Selection::single(1, 0), - }, + ("#[f|]#oo\n", "vll", "#[|foo]#\n"), + )?; + + test_key_sequence_text_result( + Args::default(), + Config::default(), + ( + indoc! {"\ + #[f|]#oo + #(b|)#ar" + }, + "vll", + indoc! {"\ + #[|foo]# + #(|bar)#" + }, + ), + )?; + + test_key_sequence_text_result( + Args::default(), + Config::default(), + ( + indoc! {"\ + #[f|]#oo + #(b|)#ar" + }, + "a", + indoc! {"\ + #[fo|]#o + #(ba|)#r" + }, + ), + )?; + + test_key_sequence_text_result( + Args::default(), + Config::default(), + ( + indoc! {"\ + #[f|]#oo + #(b|)#ar" + }, + "a", + indoc! {"\ + #[f|]#oo + #(b|)#ar" + }, + ), )?; Ok(()) @@ -155,13 +122,7 @@ mod integration { test_key_sequence_text_result( Args::default(), Config::default(), - TestCase { - in_text: "\n".into(), - in_selection: Selection::single(0, 1), - in_keys: "i(".into(), - out_text: "()\n".into(), - out_selection: Selection::single(2, 1), - }, + ("#[\n|]#", "i(", "(#[|)]#\n"), )?; test_key_sequence_text_result( @@ -173,39 +134,30 @@ mod integration { }, ..Default::default() }, - TestCase { - in_text: "\n".into(), - in_selection: Selection::single(0, 1), - in_keys: "i(".into(), - out_text: "(\n".into(), - out_selection: Selection::single(2, 1), - }, + ("#[\n|]#", "i(", "(#[|\n]#"), )?; Ok(()) } #[tokio::test] - async fn auto_indent_rs() -> anyhow::Result<()> { + async fn auto_indent_c() -> anyhow::Result<()> { test_key_sequence_text_result( Args { files: vec![(PathBuf::from("foo.c"), Position::default())], ..Default::default() }, Config::default(), - TestCase { - in_text: "void foo() {}\n".into(), - in_selection: Selection::single(13, 12), - in_keys: "i".into(), - out_text: indoc! {r#" + // switches to append mode? + ( + "void foo() {#[|}]#\n", + "i", + indoc! {"\ void foo() { - + #[|\n]#\ } - "#} - .trim_start() - .into(), - out_selection: Selection::single(16, 15), - }, + "}, + ), )?; Ok(()) diff --git a/helix-term/tests/integration/helpers.rs b/helix-term/tests/integration/helpers.rs new file mode 100644 index 00000000..d22bcc3c --- /dev/null +++ b/helix-term/tests/integration/helpers.rs @@ -0,0 +1,87 @@ +use crossterm::event::{Event, KeyEvent}; +use helix_core::{test, Selection, Transaction}; +use helix_term::{application::Application, args::Args, config::Config}; +use helix_view::{doc, input::parse_macro}; + +#[derive(Clone, Debug)] +pub struct TestCase { + pub in_text: String, + pub in_selection: Selection, + pub in_keys: String, + pub out_text: String, + pub out_selection: Selection, +} + +impl> From<(S, S, S)> for TestCase { + fn from((input, keys, output): (S, S, S)) -> Self { + let (in_text, in_selection) = test::print(&input.into()); + let (out_text, out_selection) = test::print(&output.into()); + + TestCase { + in_text, + in_selection, + in_keys: keys.into(), + out_text, + out_selection, + } + } +} + +pub fn test_key_sequence>( + app: Option, + test_case: T, + test_fn: &dyn Fn(&mut Application), +) -> anyhow::Result<()> { + let test_case = test_case.into(); + let mut app = + app.unwrap_or_else(|| Application::new(Args::default(), Config::default()).unwrap()); + + let (view, doc) = helix_view::current!(app.editor); + let sel = doc.selection(view.id).clone(); + + // replace the initial text with the input text + doc.apply( + &Transaction::change_by_selection(&doc.text(), &sel, |_| { + (0, doc.text().len_chars(), Some((&test_case.in_text).into())) + }) + .with_selection(test_case.in_selection.clone()), + view.id, + ); + + let input_keys = parse_macro(&test_case.in_keys)? + .into_iter() + .map(|key_event| Event::Key(KeyEvent::from(key_event))); + + for key in input_keys { + app.handle_terminal_events(Ok(key)); + } + + test_fn(&mut app); + + Ok(()) +} + +/// Use this for very simple test cases where there is one input +/// document, selection, and sequence of key presses, and you just +/// want to verify the resulting document and selection. +pub fn test_key_sequence_text_result>( + args: Args, + config: Config, + test_case: T, +) -> anyhow::Result<()> { + let test_case = test_case.into(); + let app = Application::new(args, config).unwrap(); + + test_key_sequence(Some(app), test_case.clone(), &|app| { + let doc = doc!(app.editor); + assert_eq!(&test_case.out_text, doc.text()); + + let mut selections: Vec<_> = doc.selections().values().cloned().collect(); + assert_eq!(1, selections.len()); + + let sel = selections.pop().unwrap(); + assert_eq!(test_case.out_selection, sel); + })?; + + Ok(()) +} -- cgit v1.2.3-70-g09d2 From 267605d147587e120d765fa62333dd986a3cb5e6 Mon Sep 17 00:00:00 2001 From: Skyler Hawthorne Date: Sun, 17 Apr 2022 00:19:22 -0400 Subject: reorganize tests into groups --- helix-term/src/application.rs | 2 +- helix-term/tests/integration.rs | 143 +--------------------------- helix-term/tests/integration/auto_indent.rs | 24 +++++ helix-term/tests/integration/auto_pairs.rs | 24 +++++ helix-term/tests/integration/movement.rs | 96 +++++++++++++++++++ 5 files changed, 148 insertions(+), 141 deletions(-) create mode 100644 helix-term/tests/integration/auto_indent.rs create mode 100644 helix-term/tests/integration/auto_pairs.rs create mode 100644 helix-term/tests/integration/movement.rs (limited to 'helix-term/tests/integration.rs') diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 146194bf..44025ea0 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -68,7 +68,7 @@ fn setup_integration_logging() { message )) }) - .level(log::LevelFilter::Debug) + .level(log::LevelFilter::Info) .chain(std::io::stdout()) .apply(); } diff --git a/helix-term/tests/integration.rs b/helix-term/tests/integration.rs index a32eebf5..a388cf6b 100644 --- a/helix-term/tests/integration.rs +++ b/helix-term/tests/integration.rs @@ -22,144 +22,7 @@ mod integration { Ok(()) } - #[tokio::test] - async fn insert_mode_cursor_position() -> anyhow::Result<()> { - test_key_sequence_text_result( - Args::default(), - Config::default(), - TestCase { - in_text: String::new(), - in_selection: Selection::single(0, 0), - in_keys: "i".into(), - out_text: String::new(), - out_selection: Selection::single(0, 0), - }, - )?; - - test_key_sequence_text_result( - Args::default(), - Config::default(), - ("#[\n|]#", "i", "#[|\n]#"), - )?; - - test_key_sequence_text_result( - Args::default(), - Config::default(), - ("#[\n|]#", "i", "#[|\n]#"), - )?; - - test_key_sequence_text_result( - Args::default(), - Config::default(), - ("#[\n|]#", "ii", "#[|\n]#"), - )?; - - Ok(()) - } - - /// Range direction is preserved when escaping insert mode to normal - #[tokio::test] - async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> { - test_key_sequence_text_result( - Args::default(), - Config::default(), - ("#[f|]#oo\n", "vll", "#[|foo]#\n"), - )?; - - test_key_sequence_text_result( - Args::default(), - Config::default(), - ( - indoc! {"\ - #[f|]#oo - #(b|)#ar" - }, - "vll", - indoc! {"\ - #[|foo]# - #(|bar)#" - }, - ), - )?; - - test_key_sequence_text_result( - Args::default(), - Config::default(), - ( - indoc! {"\ - #[f|]#oo - #(b|)#ar" - }, - "a", - indoc! {"\ - #[fo|]#o - #(ba|)#r" - }, - ), - )?; - - test_key_sequence_text_result( - Args::default(), - Config::default(), - ( - indoc! {"\ - #[f|]#oo - #(b|)#ar" - }, - "a", - indoc! {"\ - #[f|]#oo - #(b|)#ar" - }, - ), - )?; - - Ok(()) - } - - #[tokio::test] - async fn auto_pairs_basic() -> anyhow::Result<()> { - test_key_sequence_text_result( - Args::default(), - Config::default(), - ("#[\n|]#", "i(", "(#[|)]#\n"), - )?; - - test_key_sequence_text_result( - Args::default(), - Config { - editor: helix_view::editor::Config { - auto_pairs: AutoPairConfig::Enable(false), - ..Default::default() - }, - ..Default::default() - }, - ("#[\n|]#", "i(", "(#[|\n]#"), - )?; - - Ok(()) - } - - #[tokio::test] - async fn auto_indent_c() -> anyhow::Result<()> { - test_key_sequence_text_result( - Args { - files: vec![(PathBuf::from("foo.c"), Position::default())], - ..Default::default() - }, - Config::default(), - // switches to append mode? - ( - "void foo() {#[|}]#\n", - "i", - indoc! {"\ - void foo() { - #[|\n]#\ - } - "}, - ), - )?; - - Ok(()) - } + mod auto_indent; + mod auto_pairs; + mod movement; } diff --git a/helix-term/tests/integration/auto_indent.rs b/helix-term/tests/integration/auto_indent.rs new file mode 100644 index 00000000..18138cca --- /dev/null +++ b/helix-term/tests/integration/auto_indent.rs @@ -0,0 +1,24 @@ +use super::*; + +#[tokio::test] +async fn auto_indent_c() -> anyhow::Result<()> { + test_key_sequence_text_result( + Args { + files: vec![(PathBuf::from("foo.c"), Position::default())], + ..Default::default() + }, + Config::default(), + // switches to append mode? + ( + "void foo() {#[|}]#\n", + "i", + indoc! {"\ + void foo() { + #[|\n]#\ + } + "}, + ), + )?; + + Ok(()) +} diff --git a/helix-term/tests/integration/auto_pairs.rs b/helix-term/tests/integration/auto_pairs.rs new file mode 100644 index 00000000..4da44d45 --- /dev/null +++ b/helix-term/tests/integration/auto_pairs.rs @@ -0,0 +1,24 @@ +use super::*; + +#[tokio::test] +async fn auto_pairs_basic() -> anyhow::Result<()> { + test_key_sequence_text_result( + Args::default(), + Config::default(), + ("#[\n|]#", "i(", "(#[|)]#\n"), + )?; + + test_key_sequence_text_result( + Args::default(), + Config { + editor: helix_view::editor::Config { + auto_pairs: AutoPairConfig::Enable(false), + ..Default::default() + }, + ..Default::default() + }, + ("#[\n|]#", "i(", "(#[|\n]#"), + )?; + + Ok(()) +} diff --git a/helix-term/tests/integration/movement.rs b/helix-term/tests/integration/movement.rs new file mode 100644 index 00000000..d2e01e71 --- /dev/null +++ b/helix-term/tests/integration/movement.rs @@ -0,0 +1,96 @@ +use super::*; + +#[tokio::test] +async fn insert_mode_cursor_position() -> anyhow::Result<()> { + test_key_sequence_text_result( + Args::default(), + Config::default(), + TestCase { + in_text: String::new(), + in_selection: Selection::single(0, 0), + in_keys: "i".into(), + out_text: String::new(), + out_selection: Selection::single(0, 0), + }, + )?; + + test_key_sequence_text_result( + Args::default(), + Config::default(), + ("#[\n|]#", "i", "#[|\n]#"), + )?; + + test_key_sequence_text_result( + Args::default(), + Config::default(), + ("#[\n|]#", "i", "#[|\n]#"), + )?; + + test_key_sequence_text_result( + Args::default(), + Config::default(), + ("#[\n|]#", "ii", "#[|\n]#"), + )?; + + Ok(()) +} + +/// Range direction is preserved when escaping insert mode to normal +#[tokio::test] +async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> { + test_key_sequence_text_result( + Args::default(), + Config::default(), + ("#[f|]#oo\n", "vll", "#[|foo]#\n"), + )?; + + test_key_sequence_text_result( + Args::default(), + Config::default(), + ( + indoc! {"\ + #[f|]#oo + #(b|)#ar" + }, + "vll", + indoc! {"\ + #[|foo]# + #(|bar)#" + }, + ), + )?; + + test_key_sequence_text_result( + Args::default(), + Config::default(), + ( + indoc! {"\ + #[f|]#oo + #(b|)#ar" + }, + "a", + indoc! {"\ + #[fo|]#o + #(ba|)#r" + }, + ), + )?; + + test_key_sequence_text_result( + Args::default(), + Config::default(), + ( + indoc! {"\ + #[f|]#oo + #(b|)#ar" + }, + "a", + indoc! {"\ + #[f|]#oo + #(b|)#ar" + }, + ), + )?; + + Ok(()) +} -- cgit v1.2.3-70-g09d2 From 36e5809f638028644d8a51e1ed2467ea402de170 Mon Sep 17 00:00:00 2001 From: Skyler Hawthorne Date: Sun, 17 Apr 2022 21:04:59 -0400 Subject: add test for ensuring the initial cursor on a newly opened file --- Cargo.lock | 42 ++++++++++++++++++++++++++++++++ helix-term/Cargo.toml | 1 + helix-term/tests/integration.rs | 1 + helix-term/tests/integration/helpers.rs | 13 ++++++++++ helix-term/tests/integration/movement.rs | 30 +++++++++++++++++++++++ 5 files changed, 87 insertions(+) (limited to 'helix-term/tests/integration.rs') diff --git a/Cargo.lock b/Cargo.lock index 39afd141..6b49f722 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -221,6 +221,15 @@ dependencies = [ "thiserror", ] +[[package]] +name = "fastrand" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +dependencies = [ + "instant", +] + [[package]] name = "fern" version = "0.6.1" @@ -462,6 +471,7 @@ dependencies = [ "signal-hook", "signal-hook-tokio", "smallvec", + "tempfile", "tokio", "tokio-stream", "toml", @@ -555,6 +565,15 @@ dependencies = [ "unindent", ] +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + [[package]] name = "itoa" version = "1.0.1" @@ -852,6 +871,15 @@ version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + [[package]] name = "retain_mut" version = "0.1.7" @@ -1056,6 +1084,20 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + [[package]] name = "textwrap" version = "0.15.0" diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml index 05f8eed4..f1903f04 100644 --- a/helix-term/Cargo.toml +++ b/helix-term/Cargo.toml @@ -78,3 +78,4 @@ helix-loader = { version = "0.6", path = "../helix-loader" } [dev-dependencies] smallvec = "1.8" indoc = "1.0.3" +tempfile = "3.3.0" diff --git a/helix-term/tests/integration.rs b/helix-term/tests/integration.rs index a388cf6b..4b0a2346 100644 --- a/helix-term/tests/integration.rs +++ b/helix-term/tests/integration.rs @@ -25,4 +25,5 @@ mod integration { mod auto_indent; mod auto_pairs; mod movement; + mod write; } diff --git a/helix-term/tests/integration/helpers.rs b/helix-term/tests/integration/helpers.rs index d22bcc3c..5a853ad1 100644 --- a/helix-term/tests/integration/helpers.rs +++ b/helix-term/tests/integration/helpers.rs @@ -1,3 +1,5 @@ +use std::io::Write; + use crossterm::event::{Event, KeyEvent}; use helix_core::{test, Selection, Transaction}; use helix_term::{application::Application, args::Args, config::Config}; @@ -85,3 +87,14 @@ pub fn test_key_sequence_text_result>( Ok(()) } + +pub fn temp_file_with_contents>(content: S) -> tempfile::NamedTempFile { + let mut temp_file = tempfile::NamedTempFile::new().unwrap(); + temp_file + .as_file_mut() + .write_all(content.as_ref().as_bytes()) + .unwrap(); + temp_file.flush().unwrap(); + temp_file.as_file_mut().sync_all().unwrap(); + temp_file +} diff --git a/helix-term/tests/integration/movement.rs b/helix-term/tests/integration/movement.rs index d2e01e71..fc2583c1 100644 --- a/helix-term/tests/integration/movement.rs +++ b/helix-term/tests/integration/movement.rs @@ -1,3 +1,5 @@ +use helix_term::application::Application; + use super::*; #[tokio::test] @@ -94,3 +96,31 @@ async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> { Ok(()) } + +/// Ensure the very initial cursor in an opened file is the width of +/// the first grapheme +#[tokio::test] +async fn cursor_position_newly_opened_file() -> anyhow::Result<()> { + let test = |content: &str, expected_sel: Selection| { + let file = helpers::temp_file_with_contents(content); + + let mut app = Application::new( + Args { + files: vec![(file.path().to_path_buf(), Position::default())], + ..Default::default() + }, + Config::default(), + ) + .unwrap(); + + let (view, doc) = helix_view::current!(app.editor); + let sel = doc.selection(view.id).clone(); + assert_eq!(expected_sel, sel); + }; + + test("foo", Selection::single(0, 1)); + test("πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦ foo", Selection::single(0, 7)); + test("", Selection::single(0, 0)); + + Ok(()) +} -- cgit v1.2.3-70-g09d2 From ee705dcb3363aeb197f6125ab2f8285782333010 Mon Sep 17 00:00:00 2001 From: Skyler Hawthorne Date: Tue, 19 Apr 2022 01:21:31 -0400 Subject: use main application event loop Use the Application's main event loop to allow LSP, file writes, etc --- helix-core/src/auto_pairs.rs | 15 ++++--- helix-term/src/application.rs | 30 ++++++++----- helix-term/src/job.rs | 1 + helix-term/src/main.rs | 3 +- helix-term/tests/integration.rs | 3 +- helix-term/tests/integration/auto_indent.rs | 3 +- helix-term/tests/integration/auto_pairs.rs | 6 ++- helix-term/tests/integration/helpers.rs | 64 ++++++++++++++++++-------- helix-term/tests/integration/movement.rs | 24 ++++++---- helix-term/tests/integration/write.rs | 69 +++++++++++++++++++++++++++++ 10 files changed, 169 insertions(+), 49 deletions(-) create mode 100644 helix-term/tests/integration/write.rs (limited to 'helix-term/tests/integration.rs') diff --git a/helix-core/src/auto_pairs.rs b/helix-core/src/auto_pairs.rs index 1131178e..ff680a77 100644 --- a/helix-core/src/auto_pairs.rs +++ b/helix-core/src/auto_pairs.rs @@ -4,7 +4,6 @@ use crate::{graphemes, movement::Direction, Range, Rope, Selection, Tendril, Transaction}; use std::collections::HashMap; -use log::debug; use smallvec::SmallVec; // Heavily based on https://github.com/codemirror/closebrackets/ @@ -123,7 +122,7 @@ impl Default for AutoPairs { #[must_use] pub fn hook(doc: &Rope, selection: &Selection, ch: char, pairs: &AutoPairs) -> Option { - debug!("autopairs hook selection: {:#?}", selection); + log::trace!("autopairs hook selection: {:#?}", selection); if let Some(pair) = pairs.get(ch) { if pair.same() { @@ -225,9 +224,11 @@ fn get_next_range( // other end of the grapheme to get to where the new characters // are inserted, then move the head to where it should be let prev_bound = graphemes::prev_grapheme_boundary(doc_slice, start_range.head); - debug!( + log::trace!( "prev_bound: {}, offset: {}, len_inserted: {}", - prev_bound, offset, len_inserted + prev_bound, + offset, + len_inserted ); prev_bound + offset + len_inserted }; @@ -302,7 +303,7 @@ fn handle_open(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction { }); let t = transaction.with_selection(Selection::new(end_ranges, selection.primary_index())); - debug!("auto pair transaction: {:#?}", t); + log::debug!("auto pair transaction: {:#?}", t); t } @@ -334,7 +335,7 @@ fn handle_close(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction { }); let t = transaction.with_selection(Selection::new(end_ranges, selection.primary_index())); - debug!("auto pair transaction: {:#?}", t); + log::debug!("auto pair transaction: {:#?}", t); t } @@ -374,7 +375,7 @@ fn handle_same(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction { }); let t = transaction.with_selection(Selection::new(end_ranges, selection.primary_index())); - debug!("auto pair transaction: {:#?}", t); + log::debug!("auto pair transaction: {:#?}", t); t } diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 44025ea0..15026bb6 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -1,4 +1,5 @@ use arc_swap::{access::Map, ArcSwap}; +use futures_util::Stream; use helix_core::{ config::{default_syntax_loader, user_syntax_loader}, pos_at_coords, syntax, Selection, @@ -27,7 +28,7 @@ use std::{ use anyhow::Error; use crossterm::{ - event::{DisableMouseCapture, EnableMouseCapture, Event, EventStream}, + event::{DisableMouseCapture, EnableMouseCapture, Event}, execute, terminal, tty::IsTty, }; @@ -68,7 +69,7 @@ fn setup_integration_logging() { message )) }) - .level(log::LevelFilter::Info) + .level(log::LevelFilter::Debug) .chain(std::io::stdout()) .apply(); } @@ -225,8 +226,10 @@ impl Application { } } - pub async fn event_loop(&mut self) { - let mut reader = EventStream::new(); + pub async fn event_loop(&mut self, input_stream: &mut S) + where + S: Stream> + Unpin, + { let mut last_render = Instant::now(); let deadline = Duration::from_secs(1) / 60; @@ -242,7 +245,7 @@ impl Application { tokio::select! { biased; - Some(event) = reader.next() => { + Some(event) = input_stream.next() => { self.handle_terminal_events(event) } Some(signal) = self.signals.next() => { @@ -749,7 +752,10 @@ impl Application { Ok(()) } - pub async fn run(&mut self) -> Result { + pub async fn run(&mut self, input_stream: &mut S) -> Result + where + S: Stream> + Unpin, + { self.claim_term().await?; // Exit the alternate screen and disable raw mode before panicking @@ -764,16 +770,20 @@ impl Application { hook(info); })); - self.event_loop().await; + self.event_loop(input_stream).await; + self.close().await?; + self.restore_term()?; + + Ok(self.editor.exit_code) + } + pub async fn close(&mut self) -> anyhow::Result<()> { self.jobs.finish().await; if self.editor.close_language_servers(None).await.is_err() { log::error!("Timed out waiting for language servers to shutdown"); }; - self.restore_term()?; - - Ok(self.editor.exit_code) + Ok(()) } } diff --git a/helix-term/src/job.rs b/helix-term/src/job.rs index a6a77021..d21099f7 100644 --- a/helix-term/src/job.rs +++ b/helix-term/src/job.rs @@ -95,6 +95,7 @@ impl Jobs { /// Blocks until all the jobs that need to be waited on are done. pub async fn finish(&mut self) { let wait_futures = std::mem::take(&mut self.wait_futures); + log::debug!("waiting on jobs..."); wait_futures.for_each(|_| future::ready(())).await } } diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs index cd0b364b..7b26fb11 100644 --- a/helix-term/src/main.rs +++ b/helix-term/src/main.rs @@ -1,4 +1,5 @@ use anyhow::{Context, Error, Result}; +use crossterm::event::EventStream; use helix_term::application::Application; use helix_term::args::Args; use helix_term::config::Config; @@ -134,7 +135,7 @@ FLAGS: // TODO: use the thread local executor to spawn the application task separately from the work pool let mut app = Application::new(args, config).context("unable to create new application")?; - let exit_code = app.run().await?; + let exit_code = app.run(&mut EventStream::new()).await?; Ok(exit_code) } diff --git a/helix-term/tests/integration.rs b/helix-term/tests/integration.rs index 4b0a2346..b2b78e63 100644 --- a/helix-term/tests/integration.rs +++ b/helix-term/tests/integration.rs @@ -17,7 +17,8 @@ mod integration { Args::default(), Config::default(), ("#[\n|]#", "ihello world", "hello world#[|\n]#"), - )?; + ) + .await?; Ok(()) } diff --git a/helix-term/tests/integration/auto_indent.rs b/helix-term/tests/integration/auto_indent.rs index 18138cca..74d1ac58 100644 --- a/helix-term/tests/integration/auto_indent.rs +++ b/helix-term/tests/integration/auto_indent.rs @@ -18,7 +18,8 @@ async fn auto_indent_c() -> anyhow::Result<()> { } "}, ), - )?; + ) + .await?; Ok(()) } diff --git a/helix-term/tests/integration/auto_pairs.rs b/helix-term/tests/integration/auto_pairs.rs index 4da44d45..52fee55e 100644 --- a/helix-term/tests/integration/auto_pairs.rs +++ b/helix-term/tests/integration/auto_pairs.rs @@ -6,7 +6,8 @@ async fn auto_pairs_basic() -> anyhow::Result<()> { Args::default(), Config::default(), ("#[\n|]#", "i(", "(#[|)]#\n"), - )?; + ) + .await?; test_key_sequence_text_result( Args::default(), @@ -18,7 +19,8 @@ async fn auto_pairs_basic() -> anyhow::Result<()> { ..Default::default() }, ("#[\n|]#", "i(", "(#[|\n]#"), - )?; + ) + .await?; Ok(()) } diff --git a/helix-term/tests/integration/helpers.rs b/helix-term/tests/integration/helpers.rs index 5a853ad1..df662f07 100644 --- a/helix-term/tests/integration/helpers.rs +++ b/helix-term/tests/integration/helpers.rs @@ -1,9 +1,12 @@ -use std::io::Write; +use std::{io::Write, time::Duration}; +use anyhow::bail; use crossterm::event::{Event, KeyEvent}; use helix_core::{test, Selection, Transaction}; use helix_term::{application::Application, args::Args, config::Config}; use helix_view::{doc, input::parse_macro}; +use tokio::time::timeout; +use tokio_stream::wrappers::UnboundedReceiverStream; #[derive(Clone, Debug)] pub struct TestCase { @@ -29,10 +32,44 @@ impl> From<(S, S, S)> for TestCase { } } -pub fn test_key_sequence>( +pub async fn test_key_sequence( + app: &mut Application, + in_keys: &str, + test_fn: Option<&dyn Fn(&Application)>, +) -> anyhow::Result<()> { + let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); + + for key_event in parse_macro(&in_keys)?.into_iter() { + tx.send(Ok(Event::Key(KeyEvent::from(key_event))))?; + } + + let mut rx_stream = UnboundedReceiverStream::new(rx); + let event_loop = app.event_loop(&mut rx_stream); + let result = timeout(Duration::from_millis(500), event_loop).await; + + if result.is_ok() { + bail!("application exited before test function could run"); + } + + if let Some(test) = test_fn { + test(app); + }; + + for key_event in parse_macro(":q!")?.into_iter() { + tx.send(Ok(Event::Key(KeyEvent::from(key_event))))?; + } + + let event_loop = app.event_loop(&mut rx_stream); + timeout(Duration::from_millis(5000), event_loop).await?; + app.close().await?; + + Ok(()) +} + +pub async fn test_key_sequence_with_input_text>( app: Option, test_case: T, - test_fn: &dyn Fn(&mut Application), + test_fn: &dyn Fn(&Application), ) -> anyhow::Result<()> { let test_case = test_case.into(); let mut app = @@ -50,23 +87,13 @@ pub fn test_key_sequence>( view.id, ); - let input_keys = parse_macro(&test_case.in_keys)? - .into_iter() - .map(|key_event| Event::Key(KeyEvent::from(key_event))); - - for key in input_keys { - app.handle_terminal_events(Ok(key)); - } - - test_fn(&mut app); - - Ok(()) + test_key_sequence(&mut app, &test_case.in_keys, Some(test_fn)).await } /// Use this for very simple test cases where there is one input /// document, selection, and sequence of key presses, and you just /// want to verify the resulting document and selection. -pub fn test_key_sequence_text_result>( +pub async fn test_key_sequence_text_result>( args: Args, config: Config, test_case: T, @@ -74,7 +101,7 @@ pub fn test_key_sequence_text_result>( let test_case = test_case.into(); let app = Application::new(args, config).unwrap(); - test_key_sequence(Some(app), test_case.clone(), &|app| { + test_key_sequence_with_input_text(Some(app), test_case.clone(), &|app| { let doc = doc!(app.editor); assert_eq!(&test_case.out_text, doc.text()); @@ -83,9 +110,8 @@ pub fn test_key_sequence_text_result>( let sel = selections.pop().unwrap(); assert_eq!(test_case.out_selection, sel); - })?; - - Ok(()) + }) + .await } pub fn temp_file_with_contents>(content: S) -> tempfile::NamedTempFile { diff --git a/helix-term/tests/integration/movement.rs b/helix-term/tests/integration/movement.rs index fc2583c1..cac10852 100644 --- a/helix-term/tests/integration/movement.rs +++ b/helix-term/tests/integration/movement.rs @@ -14,25 +14,29 @@ async fn insert_mode_cursor_position() -> anyhow::Result<()> { out_text: String::new(), out_selection: Selection::single(0, 0), }, - )?; + ) + .await?; test_key_sequence_text_result( Args::default(), Config::default(), ("#[\n|]#", "i", "#[|\n]#"), - )?; + ) + .await?; test_key_sequence_text_result( Args::default(), Config::default(), ("#[\n|]#", "i", "#[|\n]#"), - )?; + ) + .await?; test_key_sequence_text_result( Args::default(), Config::default(), ("#[\n|]#", "ii", "#[|\n]#"), - )?; + ) + .await?; Ok(()) } @@ -44,7 +48,8 @@ async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> { Args::default(), Config::default(), ("#[f|]#oo\n", "vll", "#[|foo]#\n"), - )?; + ) + .await?; test_key_sequence_text_result( Args::default(), @@ -60,7 +65,8 @@ async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> { #(|bar)#" }, ), - )?; + ) + .await?; test_key_sequence_text_result( Args::default(), @@ -76,7 +82,8 @@ async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> { #(ba|)#r" }, ), - )?; + ) + .await?; test_key_sequence_text_result( Args::default(), @@ -92,7 +99,8 @@ async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> { #(b|)#ar" }, ), - )?; + ) + .await?; Ok(()) } diff --git a/helix-term/tests/integration/write.rs b/helix-term/tests/integration/write.rs new file mode 100644 index 00000000..47e56288 --- /dev/null +++ b/helix-term/tests/integration/write.rs @@ -0,0 +1,69 @@ +use std::{ + io::{Read, Write}, + ops::RangeInclusive, +}; + +use helix_term::application::Application; + +use super::*; + +#[tokio::test] +async fn test_write() -> anyhow::Result<()> { + let mut file = tempfile::NamedTempFile::new().unwrap(); + + test_key_sequence( + &mut Application::new( + Args { + files: vec![(file.path().to_path_buf(), Position::default())], + ..Default::default() + }, + Config::default(), + )?, + "ii can eat glass, it will not hurt me:w", + None, + ) + .await?; + + file.as_file_mut().flush()?; + file.as_file_mut().sync_all()?; + + let mut file_content = String::new(); + file.as_file_mut().read_to_string(&mut file_content)?; + assert_eq!("i can eat glass, it will not hurt me\n", file_content); + + Ok(()) +} + +#[tokio::test] +async fn test_write_concurrent() -> anyhow::Result<()> { + let mut file = tempfile::NamedTempFile::new().unwrap(); + let mut command = String::new(); + const RANGE: RangeInclusive = 1..=1000; + + for i in RANGE { + let cmd = format!("%c{}:w", i); + command.push_str(&cmd); + } + + test_key_sequence( + &mut Application::new( + Args { + files: vec![(file.path().to_path_buf(), Position::default())], + ..Default::default() + }, + Config::default(), + )?, + &command, + None, + ) + .await?; + + file.as_file_mut().flush()?; + file.as_file_mut().sync_all()?; + + let mut file_content = String::new(); + file.as_file_mut().read_to_string(&mut file_content)?; + assert_eq!(RANGE.end().to_string(), file_content); + + Ok(()) +} -- cgit v1.2.3-70-g09d2 From 07fc80aece221233b4a986b0c5a03e2056cc1307 Mon Sep 17 00:00:00 2001 From: Skyler Hawthorne Date: Sat, 30 Apr 2022 20:44:54 -0400 Subject: tests for serialized writes --- helix-term/tests/integration.rs | 2 + helix-term/tests/integration/auto_indent.rs | 1 + helix-term/tests/integration/auto_pairs.rs | 2 + helix-term/tests/integration/commands.rs | 25 +++++++++ helix-term/tests/integration/helpers.rs | 69 ++++++++++++++++--------- helix-term/tests/integration/movement.rs | 8 +++ helix-term/tests/integration/write.rs | 79 ++++++++++++++++++++++------- helix-view/src/editor.rs | 5 ++ 8 files changed, 147 insertions(+), 44 deletions(-) create mode 100644 helix-term/tests/integration/commands.rs (limited to 'helix-term/tests/integration.rs') diff --git a/helix-term/tests/integration.rs b/helix-term/tests/integration.rs index b2b78e63..54364e12 100644 --- a/helix-term/tests/integration.rs +++ b/helix-term/tests/integration.rs @@ -17,6 +17,7 @@ mod integration { Args::default(), Config::default(), ("#[\n|]#", "ihello world", "hello world#[|\n]#"), + None, ) .await?; @@ -25,6 +26,7 @@ mod integration { mod auto_indent; mod auto_pairs; + mod commands; mod movement; mod write; } diff --git a/helix-term/tests/integration/auto_indent.rs b/helix-term/tests/integration/auto_indent.rs index 74d1ac58..fdfc7dfb 100644 --- a/helix-term/tests/integration/auto_indent.rs +++ b/helix-term/tests/integration/auto_indent.rs @@ -18,6 +18,7 @@ async fn auto_indent_c() -> anyhow::Result<()> { } "}, ), + None, ) .await?; diff --git a/helix-term/tests/integration/auto_pairs.rs b/helix-term/tests/integration/auto_pairs.rs index 52fee55e..d34cd0fd 100644 --- a/helix-term/tests/integration/auto_pairs.rs +++ b/helix-term/tests/integration/auto_pairs.rs @@ -6,6 +6,7 @@ async fn auto_pairs_basic() -> anyhow::Result<()> { Args::default(), Config::default(), ("#[\n|]#", "i(", "(#[|)]#\n"), + None, ) .await?; @@ -19,6 +20,7 @@ async fn auto_pairs_basic() -> anyhow::Result<()> { ..Default::default() }, ("#[\n|]#", "i(", "(#[|\n]#"), + None, ) .await?; diff --git a/helix-term/tests/integration/commands.rs b/helix-term/tests/integration/commands.rs new file mode 100644 index 00000000..ec60ac96 --- /dev/null +++ b/helix-term/tests/integration/commands.rs @@ -0,0 +1,25 @@ +use helix_core::diagnostic::Severity; +use helix_term::application::Application; + +use super::*; + +#[tokio::test] +async fn test_write_quit_fail() -> anyhow::Result<()> { + test_key_sequence( + &mut Application::new( + Args { + files: vec![(PathBuf::from("/foo"), Position::default())], + ..Default::default() + }, + Config::default(), + )?, + "ihello:wq", + Some(&|app| { + assert_eq!(&Severity::Error, app.editor.get_status().unwrap().1); + }), + None, + ) + .await?; + + Ok(()) +} diff --git a/helix-term/tests/integration/helpers.rs b/helix-term/tests/integration/helpers.rs index df662f07..60bfa331 100644 --- a/helix-term/tests/integration/helpers.rs +++ b/helix-term/tests/integration/helpers.rs @@ -5,7 +5,6 @@ use crossterm::event::{Event, KeyEvent}; use helix_core::{test, Selection, Transaction}; use helix_term::{application::Application, args::Args, config::Config}; use helix_view::{doc, input::parse_macro}; -use tokio::time::timeout; use tokio_stream::wrappers::UnboundedReceiverStream; #[derive(Clone, Debug)] @@ -32,35 +31,48 @@ impl> From<(S, S, S)> for TestCase { } } +#[inline] pub async fn test_key_sequence( app: &mut Application, in_keys: &str, test_fn: Option<&dyn Fn(&Application)>, + timeout: Option, ) -> anyhow::Result<()> { + test_key_sequences(app, vec![(in_keys, test_fn)], timeout).await +} + +pub async fn test_key_sequences( + app: &mut Application, + inputs: Vec<(&str, Option<&dyn Fn(&Application)>)>, + timeout: Option, +) -> anyhow::Result<()> { + let timeout = timeout.unwrap_or(Duration::from_millis(500)); let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); + let mut rx_stream = UnboundedReceiverStream::new(rx); - for key_event in parse_macro(&in_keys)?.into_iter() { - tx.send(Ok(Event::Key(KeyEvent::from(key_event))))?; - } + for (in_keys, test_fn) in inputs { + for key_event in parse_macro(&in_keys)?.into_iter() { + tx.send(Ok(Event::Key(KeyEvent::from(key_event))))?; + } - let mut rx_stream = UnboundedReceiverStream::new(rx); - let event_loop = app.event_loop(&mut rx_stream); - let result = timeout(Duration::from_millis(500), event_loop).await; + let event_loop = app.event_loop(&mut rx_stream); + let result = tokio::time::timeout(timeout, event_loop).await; - if result.is_ok() { - bail!("application exited before test function could run"); - } + if result.is_ok() { + bail!("application exited before test function could run"); + } - if let Some(test) = test_fn { - test(app); - }; + if let Some(test) = test_fn { + test(app); + }; + } for key_event in parse_macro(":q!")?.into_iter() { tx.send(Ok(Event::Key(KeyEvent::from(key_event))))?; } let event_loop = app.event_loop(&mut rx_stream); - timeout(Duration::from_millis(5000), event_loop).await?; + tokio::time::timeout(timeout, event_loop).await?; app.close().await?; Ok(()) @@ -70,6 +82,7 @@ pub async fn test_key_sequence_with_input_text>( app: Option, test_case: T, test_fn: &dyn Fn(&Application), + timeout: Option, ) -> anyhow::Result<()> { let test_case = test_case.into(); let mut app = @@ -87,7 +100,7 @@ pub async fn test_key_sequence_with_input_text>( view.id, ); - test_key_sequence(&mut app, &test_case.in_keys, Some(test_fn)).await + test_key_sequence(&mut app, &test_case.in_keys, Some(test_fn), timeout).await } /// Use this for very simple test cases where there is one input @@ -97,20 +110,26 @@ pub async fn test_key_sequence_text_result>( args: Args, config: Config, test_case: T, + timeout: Option, ) -> anyhow::Result<()> { let test_case = test_case.into(); let app = Application::new(args, config).unwrap(); - test_key_sequence_with_input_text(Some(app), test_case.clone(), &|app| { - let doc = doc!(app.editor); - assert_eq!(&test_case.out_text, doc.text()); - - let mut selections: Vec<_> = doc.selections().values().cloned().collect(); - assert_eq!(1, selections.len()); - - let sel = selections.pop().unwrap(); - assert_eq!(test_case.out_selection, sel); - }) + test_key_sequence_with_input_text( + Some(app), + test_case.clone(), + &|app| { + let doc = doc!(app.editor); + assert_eq!(&test_case.out_text, doc.text()); + + let mut selections: Vec<_> = doc.selections().values().cloned().collect(); + assert_eq!(1, selections.len()); + + let sel = selections.pop().unwrap(); + assert_eq!(test_case.out_selection, sel); + }, + timeout, + ) .await } diff --git a/helix-term/tests/integration/movement.rs b/helix-term/tests/integration/movement.rs index cac10852..a1d294c6 100644 --- a/helix-term/tests/integration/movement.rs +++ b/helix-term/tests/integration/movement.rs @@ -14,6 +14,7 @@ async fn insert_mode_cursor_position() -> anyhow::Result<()> { out_text: String::new(), out_selection: Selection::single(0, 0), }, + None, ) .await?; @@ -21,6 +22,7 @@ async fn insert_mode_cursor_position() -> anyhow::Result<()> { Args::default(), Config::default(), ("#[\n|]#", "i", "#[|\n]#"), + None, ) .await?; @@ -28,6 +30,7 @@ async fn insert_mode_cursor_position() -> anyhow::Result<()> { Args::default(), Config::default(), ("#[\n|]#", "i", "#[|\n]#"), + None, ) .await?; @@ -35,6 +38,7 @@ async fn insert_mode_cursor_position() -> anyhow::Result<()> { Args::default(), Config::default(), ("#[\n|]#", "ii", "#[|\n]#"), + None, ) .await?; @@ -48,6 +52,7 @@ async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> { Args::default(), Config::default(), ("#[f|]#oo\n", "vll", "#[|foo]#\n"), + None, ) .await?; @@ -65,6 +70,7 @@ async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> { #(|bar)#" }, ), + None, ) .await?; @@ -82,6 +88,7 @@ async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> { #(ba|)#r" }, ), + None, ) .await?; @@ -99,6 +106,7 @@ async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> { #(b|)#ar" }, ), + None, ) .await?; diff --git a/helix-term/tests/integration/write.rs b/helix-term/tests/integration/write.rs index 47e56288..27f97a45 100644 --- a/helix-term/tests/integration/write.rs +++ b/helix-term/tests/integration/write.rs @@ -1,9 +1,11 @@ use std::{ io::{Read, Write}, - ops::RangeInclusive, + time::Duration, }; +use helix_core::diagnostic::Severity; use helix_term::application::Application; +use helix_view::doc; use super::*; @@ -21,6 +23,7 @@ async fn test_write() -> anyhow::Result<()> { )?, "ii can eat glass, it will not hurt me:w", None, + Some(Duration::from_millis(1000)), ) .await?; @@ -35,35 +38,73 @@ async fn test_write() -> anyhow::Result<()> { } #[tokio::test] -async fn test_write_concurrent() -> anyhow::Result<()> { - let mut file = tempfile::NamedTempFile::new().unwrap(); - let mut command = String::new(); - const RANGE: RangeInclusive = 1..=1000; - - for i in RANGE { - let cmd = format!("%c{}:w", i); - command.push_str(&cmd); - } - - test_key_sequence( +async fn test_write_fail_mod_flag() -> anyhow::Result<()> { + test_key_sequences( &mut Application::new( Args { - files: vec![(file.path().to_path_buf(), Position::default())], + files: vec![(PathBuf::from("/foo"), Position::default())], ..Default::default() }, Config::default(), )?, - &command, + vec![ + ( + "", + Some(&|app| { + let doc = doc!(app.editor); + assert!(!doc.is_modified()); + }), + ), + ( + "ihello", + Some(&|app| { + let doc = doc!(app.editor); + assert!(doc.is_modified()); + }), + ), + ( + ":w", + Some(&|app| { + assert_eq!(&Severity::Error, app.editor.get_status().unwrap().1); + + let doc = doc!(app.editor); + assert!(doc.is_modified()); + }), + ), + ], None, ) .await?; - file.as_file_mut().flush()?; - file.as_file_mut().sync_all()?; + Ok(()) +} - let mut file_content = String::new(); - file.as_file_mut().read_to_string(&mut file_content)?; - assert_eq!(RANGE.end().to_string(), file_content); +#[tokio::test] +#[ignore] +async fn test_write_fail_new_path() -> anyhow::Result<()> { + test_key_sequences( + &mut Application::new(Args::default(), Config::default())?, + vec![ + ( + "", + Some(&|app| { + let doc = doc!(app.editor); + assert_eq!(None, app.editor.get_status()); + assert_eq!(None, doc.path()); + }), + ), + ( + ":w /foo", + Some(&|app| { + let doc = doc!(app.editor); + assert_eq!(&Severity::Error, app.editor.get_status().unwrap().1); + assert_eq!(None, doc.path()); + }), + ), + ], + Some(Duration::from_millis(1000)), + ) + .await?; Ok(()) } diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 8607c65a..d828f9ec 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -571,6 +571,11 @@ impl Editor { self.status_msg = Some((error.into(), Severity::Error)); } + #[inline] + pub fn get_status(&self) -> Option<(&Cow<'static, str>, &Severity)> { + self.status_msg.as_ref().map(|(status, sev)| (status, sev)) + } + pub fn set_theme(&mut self, theme: Theme) { // `ui.selection` is the only scope required to be able to render a theme. if theme.find_scope_index("ui.selection").is_none() { -- cgit v1.2.3-70-g09d2 From 2386c81ebc118860107094591b76ef3864e120a8 Mon Sep 17 00:00:00 2001 From: Skyler Hawthorne Date: Sat, 23 Apr 2022 21:49:31 -0400 Subject: use idle timer instead of fixed timeout --- helix-term/src/application.rs | 37 ++++++++++++++++++++----- helix-term/tests/integration.rs | 1 - helix-term/tests/integration/auto_indent.rs | 1 - helix-term/tests/integration/auto_pairs.rs | 2 -- helix-term/tests/integration/commands.rs | 4 --- helix-term/tests/integration/helpers.rs | 42 +++++++++++------------------ helix-term/tests/integration/movement.rs | 8 ------ helix-term/tests/integration/write.rs | 5 ---- 8 files changed, 46 insertions(+), 54 deletions(-) (limited to 'helix-term/tests/integration.rs') diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 15026bb6..3b96c45a 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -40,6 +40,8 @@ use { #[cfg(windows)] type Signals = futures_util::stream::Empty<()>; +const LSP_DEADLINE: Duration = Duration::from_millis(16); + pub struct Application { compositor: Compositor, pub editor: Editor, @@ -54,6 +56,7 @@ pub struct Application { signals: Signals, jobs: Jobs, lsp_progress: LspProgressMap, + last_render: Instant, } #[cfg(feature = "integration")] @@ -203,6 +206,7 @@ impl Application { signals, jobs: Jobs::new(), lsp_progress: LspProgressMap::new(), + last_render: Instant::now(), }; Ok(app) @@ -230,58 +234,79 @@ impl Application { where S: Stream> + Unpin, { - let mut last_render = Instant::now(); - let deadline = Duration::from_secs(1) / 60; - self.render(); + self.last_render = Instant::now(); loop { if self.editor.should_close() { break; } + self.event_loop_until_idle(input_stream).await; + } + } + + pub async fn event_loop_until_idle(&mut self, input_stream: &mut S) -> bool + where + S: Stream> + Unpin, + { + loop { + if self.editor.should_close() { + return false; + } + use futures_util::StreamExt; tokio::select! { biased; Some(event) = input_stream.next() => { - self.handle_terminal_events(event) + self.handle_terminal_events(event); + self.editor.reset_idle_timer(); } Some(signal) = self.signals.next() => { self.handle_signals(signal).await; + self.editor.reset_idle_timer(); } Some((id, call)) = self.editor.language_servers.incoming.next() => { self.handle_language_server_message(call, id).await; // limit render calls for fast language server messages let last = self.editor.language_servers.incoming.is_empty(); - if last || last_render.elapsed() > deadline { + + if last || self.last_render.elapsed() > LSP_DEADLINE { self.render(); - last_render = Instant::now(); + self.last_render = Instant::now(); } + + self.editor.reset_idle_timer(); } Some(payload) = self.editor.debugger_events.next() => { let needs_render = self.editor.handle_debugger_message(payload).await; if needs_render { self.render(); } + self.editor.reset_idle_timer(); } Some(config_event) = self.editor.config_events.1.recv() => { self.handle_config_events(config_event); self.render(); + self.editor.reset_idle_timer(); } Some(callback) = self.jobs.futures.next() => { self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback); self.render(); + self.editor.reset_idle_timer(); } Some(callback) = self.jobs.wait_futures.next() => { self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback); self.render(); + self.editor.reset_idle_timer(); } _ = &mut self.editor.idle_timer => { // idle timeout self.editor.clear_idle_timer(); self.handle_idle_timeout(); + return true; } } } diff --git a/helix-term/tests/integration.rs b/helix-term/tests/integration.rs index 54364e12..061bd8ff 100644 --- a/helix-term/tests/integration.rs +++ b/helix-term/tests/integration.rs @@ -17,7 +17,6 @@ mod integration { Args::default(), Config::default(), ("#[\n|]#", "ihello world", "hello world#[|\n]#"), - None, ) .await?; diff --git a/helix-term/tests/integration/auto_indent.rs b/helix-term/tests/integration/auto_indent.rs index fdfc7dfb..74d1ac58 100644 --- a/helix-term/tests/integration/auto_indent.rs +++ b/helix-term/tests/integration/auto_indent.rs @@ -18,7 +18,6 @@ async fn auto_indent_c() -> anyhow::Result<()> { } "}, ), - None, ) .await?; diff --git a/helix-term/tests/integration/auto_pairs.rs b/helix-term/tests/integration/auto_pairs.rs index d34cd0fd..52fee55e 100644 --- a/helix-term/tests/integration/auto_pairs.rs +++ b/helix-term/tests/integration/auto_pairs.rs @@ -6,7 +6,6 @@ async fn auto_pairs_basic() -> anyhow::Result<()> { Args::default(), Config::default(), ("#[\n|]#", "i(", "(#[|)]#\n"), - None, ) .await?; @@ -20,7 +19,6 @@ async fn auto_pairs_basic() -> anyhow::Result<()> { ..Default::default() }, ("#[\n|]#", "i(", "(#[|\n]#"), - None, ) .await?; diff --git a/helix-term/tests/integration/commands.rs b/helix-term/tests/integration/commands.rs index 7da180b9..4ab87b9b 100644 --- a/helix-term/tests/integration/commands.rs +++ b/helix-term/tests/integration/commands.rs @@ -1,7 +1,6 @@ use std::{ io::{Read, Write}, ops::RangeInclusive, - time::Duration, }; use helix_core::diagnostic::Severity; @@ -23,7 +22,6 @@ async fn test_write_quit_fail() -> anyhow::Result<()> { Some(&|app| { assert_eq!(&Severity::Error, app.editor.get_status().unwrap().1); }), - None, ) .await?; @@ -57,7 +55,6 @@ async fn test_buffer_close() -> anyhow::Result<()> { }), ), ], - None, ) .await?; @@ -88,7 +85,6 @@ async fn test_buffer_close() -> anyhow::Result<()> { let doc = app.editor.document_by_path(file.path()); assert!(doc.is_none(), "found doc: {:?}", doc); }), - Some(Duration::from_millis(5000)), ) .await?; diff --git a/helix-term/tests/integration/helpers.rs b/helix-term/tests/integration/helpers.rs index 18a3517c..29cb8cd8 100644 --- a/helix-term/tests/integration/helpers.rs +++ b/helix-term/tests/integration/helpers.rs @@ -36,17 +36,15 @@ pub async fn test_key_sequence( app: &mut Application, in_keys: Option<&str>, test_fn: Option<&dyn Fn(&Application)>, - timeout: Option, ) -> anyhow::Result<()> { - test_key_sequences(app, vec![(in_keys, test_fn)], timeout).await + test_key_sequences(app, vec![(in_keys, test_fn)]).await } pub async fn test_key_sequences( app: &mut Application, inputs: Vec<(Option<&str>, Option<&dyn Fn(&Application)>)>, - timeout: Option, ) -> anyhow::Result<()> { - let timeout = timeout.unwrap_or(Duration::from_millis(500)); + const TIMEOUT: Duration = Duration::from_millis(500); let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); let mut rx_stream = UnboundedReceiverStream::new(rx); @@ -57,10 +55,7 @@ pub async fn test_key_sequences( } } - let event_loop = app.event_loop(&mut rx_stream); - let result = tokio::time::timeout(timeout, event_loop).await; - - if result.is_ok() { + if !app.event_loop_until_idle(&mut rx_stream).await { bail!("application exited before test function could run"); } @@ -74,7 +69,7 @@ pub async fn test_key_sequences( } let event_loop = app.event_loop(&mut rx_stream); - tokio::time::timeout(timeout, event_loop).await?; + tokio::time::timeout(TIMEOUT, event_loop).await?; app.close().await?; Ok(()) @@ -84,7 +79,6 @@ pub async fn test_key_sequence_with_input_text>( app: Option, test_case: T, test_fn: &dyn Fn(&Application), - timeout: Option, ) -> anyhow::Result<()> { let test_case = test_case.into(); let mut app = @@ -102,7 +96,7 @@ pub async fn test_key_sequence_with_input_text>( view.id, ); - test_key_sequence(&mut app, Some(&test_case.in_keys), Some(test_fn), timeout).await + test_key_sequence(&mut app, Some(&test_case.in_keys), Some(test_fn)).await } /// Use this for very simple test cases where there is one input @@ -112,26 +106,20 @@ pub async fn test_key_sequence_text_result>( args: Args, config: Config, test_case: T, - timeout: Option, ) -> anyhow::Result<()> { let test_case = test_case.into(); let app = Application::new(args, config).unwrap(); - test_key_sequence_with_input_text( - Some(app), - test_case.clone(), - &|app| { - let doc = doc!(app.editor); - assert_eq!(&test_case.out_text, doc.text()); - - let mut selections: Vec<_> = doc.selections().values().cloned().collect(); - assert_eq!(1, selections.len()); - - let sel = selections.pop().unwrap(); - assert_eq!(test_case.out_selection, sel); - }, - timeout, - ) + test_key_sequence_with_input_text(Some(app), test_case.clone(), &|app| { + let doc = doc!(app.editor); + assert_eq!(&test_case.out_text, doc.text()); + + let mut selections: Vec<_> = doc.selections().values().cloned().collect(); + assert_eq!(1, selections.len()); + + let sel = selections.pop().unwrap(); + assert_eq!(test_case.out_selection, sel); + }) .await } diff --git a/helix-term/tests/integration/movement.rs b/helix-term/tests/integration/movement.rs index a1d294c6..cac10852 100644 --- a/helix-term/tests/integration/movement.rs +++ b/helix-term/tests/integration/movement.rs @@ -14,7 +14,6 @@ async fn insert_mode_cursor_position() -> anyhow::Result<()> { out_text: String::new(), out_selection: Selection::single(0, 0), }, - None, ) .await?; @@ -22,7 +21,6 @@ async fn insert_mode_cursor_position() -> anyhow::Result<()> { Args::default(), Config::default(), ("#[\n|]#", "i", "#[|\n]#"), - None, ) .await?; @@ -30,7 +28,6 @@ async fn insert_mode_cursor_position() -> anyhow::Result<()> { Args::default(), Config::default(), ("#[\n|]#", "i", "#[|\n]#"), - None, ) .await?; @@ -38,7 +35,6 @@ async fn insert_mode_cursor_position() -> anyhow::Result<()> { Args::default(), Config::default(), ("#[\n|]#", "ii", "#[|\n]#"), - None, ) .await?; @@ -52,7 +48,6 @@ async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> { Args::default(), Config::default(), ("#[f|]#oo\n", "vll", "#[|foo]#\n"), - None, ) .await?; @@ -70,7 +65,6 @@ async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> { #(|bar)#" }, ), - None, ) .await?; @@ -88,7 +82,6 @@ async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> { #(ba|)#r" }, ), - None, ) .await?; @@ -106,7 +99,6 @@ async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> { #(b|)#ar" }, ), - None, ) .await?; diff --git a/helix-term/tests/integration/write.rs b/helix-term/tests/integration/write.rs index 365e6b8d..0cc41dbc 100644 --- a/helix-term/tests/integration/write.rs +++ b/helix-term/tests/integration/write.rs @@ -1,7 +1,6 @@ use std::{ io::{Read, Write}, ops::RangeInclusive, - time::Duration, }; use helix_core::diagnostic::Severity; @@ -24,7 +23,6 @@ async fn test_write() -> anyhow::Result<()> { )?, Some("ii can eat glass, it will not hurt me:w"), None, - Some(Duration::from_millis(1000)), ) .await?; @@ -59,7 +57,6 @@ async fn test_write_concurrent() -> anyhow::Result<()> { )?, Some(&command), None, - Some(Duration::from_millis(10000)), ) .await?; @@ -108,7 +105,6 @@ async fn test_write_fail_mod_flag() -> anyhow::Result<()> { }), ), ], - None, ) .await?; @@ -138,7 +134,6 @@ async fn test_write_fail_new_path() -> anyhow::Result<()> { }), ), ], - Some(Duration::from_millis(1000)), ) .await?; -- cgit v1.2.3-70-g09d2 From 8d8d389536d1f948f25a38c33f278a5e2f8d1b28 Mon Sep 17 00:00:00 2001 From: Skyler Hawthorne Date: Sat, 30 Apr 2022 09:22:20 -0400 Subject: rename top level module to satisfy cargo fmt --- helix-term/tests/integration.rs | 2 +- helix-term/tests/integration/auto_indent.rs | 26 ----- helix-term/tests/integration/auto_pairs.rs | 26 ----- helix-term/tests/integration/commands.rs | 101 ----------------- helix-term/tests/integration/helpers.rs | 169 ---------------------------- helix-term/tests/integration/movement.rs | 135 ---------------------- helix-term/tests/integration/write.rs | 153 ------------------------- helix-term/tests/test/auto_indent.rs | 26 +++++ helix-term/tests/test/auto_pairs.rs | 26 +++++ helix-term/tests/test/commands.rs | 101 +++++++++++++++++ helix-term/tests/test/helpers.rs | 169 ++++++++++++++++++++++++++++ helix-term/tests/test/movement.rs | 135 ++++++++++++++++++++++ helix-term/tests/test/write.rs | 153 +++++++++++++++++++++++++ 13 files changed, 611 insertions(+), 611 deletions(-) delete mode 100644 helix-term/tests/integration/auto_indent.rs delete mode 100644 helix-term/tests/integration/auto_pairs.rs delete mode 100644 helix-term/tests/integration/commands.rs delete mode 100644 helix-term/tests/integration/helpers.rs delete mode 100644 helix-term/tests/integration/movement.rs delete mode 100644 helix-term/tests/integration/write.rs create mode 100644 helix-term/tests/test/auto_indent.rs create mode 100644 helix-term/tests/test/auto_pairs.rs create mode 100644 helix-term/tests/test/commands.rs create mode 100644 helix-term/tests/test/helpers.rs create mode 100644 helix-term/tests/test/movement.rs create mode 100644 helix-term/tests/test/write.rs (limited to 'helix-term/tests/integration.rs') diff --git a/helix-term/tests/integration.rs b/helix-term/tests/integration.rs index 061bd8ff..376bc88b 100644 --- a/helix-term/tests/integration.rs +++ b/helix-term/tests/integration.rs @@ -1,5 +1,5 @@ #[cfg(feature = "integration")] -mod integration { +mod test { mod helpers; use std::path::PathBuf; diff --git a/helix-term/tests/integration/auto_indent.rs b/helix-term/tests/integration/auto_indent.rs deleted file mode 100644 index 8933cb6a..00000000 --- a/helix-term/tests/integration/auto_indent.rs +++ /dev/null @@ -1,26 +0,0 @@ -use super::*; - -#[tokio::test] -async fn auto_indent_c() -> anyhow::Result<()> { - test_key_sequence_text_result( - Args { - files: vec![(PathBuf::from("foo.c"), Position::default())], - ..Default::default() - }, - Config::default(), - // switches to append mode? - ( - helpers::platform_line("void foo() {#[|}]#").as_ref(), - "i", - helpers::platform_line(indoc! {"\ - void foo() { - #[|\n]#\ - } - "}) - .as_ref(), - ), - ) - .await?; - - Ok(()) -} diff --git a/helix-term/tests/integration/auto_pairs.rs b/helix-term/tests/integration/auto_pairs.rs deleted file mode 100644 index 52fee55e..00000000 --- a/helix-term/tests/integration/auto_pairs.rs +++ /dev/null @@ -1,26 +0,0 @@ -use super::*; - -#[tokio::test] -async fn auto_pairs_basic() -> anyhow::Result<()> { - test_key_sequence_text_result( - Args::default(), - Config::default(), - ("#[\n|]#", "i(", "(#[|)]#\n"), - ) - .await?; - - test_key_sequence_text_result( - Args::default(), - Config { - editor: helix_view::editor::Config { - auto_pairs: AutoPairConfig::Enable(false), - ..Default::default() - }, - ..Default::default() - }, - ("#[\n|]#", "i(", "(#[|\n]#"), - ) - .await?; - - Ok(()) -} diff --git a/helix-term/tests/integration/commands.rs b/helix-term/tests/integration/commands.rs deleted file mode 100644 index 1ff7cc90..00000000 --- a/helix-term/tests/integration/commands.rs +++ /dev/null @@ -1,101 +0,0 @@ -use std::{ - io::{Read, Write}, - ops::RangeInclusive, -}; - -use helix_core::diagnostic::Severity; -use helix_term::application::Application; - -use super::*; - -#[tokio::test] -async fn test_write_quit_fail() -> anyhow::Result<()> { - let file = helpers::new_readonly_tempfile()?; - - test_key_sequence( - &mut Application::new( - Args { - files: vec![(file.path().to_path_buf(), Position::default())], - ..Default::default() - }, - Config::default(), - )?, - Some("ihello:wq"), - Some(&|app| { - assert_eq!(&Severity::Error, app.editor.get_status().unwrap().1); - }), - ) - .await?; - - Ok(()) -} - -#[tokio::test] -async fn test_buffer_close() -> anyhow::Result<()> { - test_key_sequences( - &mut Application::new(Args::default(), Config::default())?, - vec![ - ( - None, - Some(&|app| { - assert_eq!(1, app.editor.documents().count()); - assert!(!app.editor.is_err()); - }), - ), - ( - Some("ihello:new"), - Some(&|app| { - assert_eq!(2, app.editor.documents().count()); - assert!(!app.editor.is_err()); - }), - ), - ( - Some(":bufferclose"), - Some(&|app| { - assert_eq!(1, app.editor.documents().count()); - assert!(!app.editor.is_err()); - }), - ), - ], - ) - .await?; - - // verify if writes are queued up, it finishes them before closing the buffer - let mut file = tempfile::NamedTempFile::new()?; - let mut command = String::new(); - const RANGE: RangeInclusive = 1..=10; - - for i in RANGE { - let cmd = format!("%c{}:w", i); - command.push_str(&cmd); - } - - command.push_str(":bufferclose"); - - test_key_sequence( - &mut Application::new( - Args { - files: vec![(file.path().to_path_buf(), Position::default())], - ..Default::default() - }, - Config::default(), - )?, - Some(&command), - Some(&|app| { - assert!(!app.editor.is_err(), "error: {:?}", app.editor.get_status()); - - let doc = app.editor.document_by_path(file.path()); - assert!(doc.is_none(), "found doc: {:?}", doc); - }), - ) - .await?; - - file.as_file_mut().flush()?; - file.as_file_mut().sync_all()?; - - let mut file_content = String::new(); - file.as_file_mut().read_to_string(&mut file_content)?; - assert_eq!(RANGE.end().to_string(), file_content); - - Ok(()) -} diff --git a/helix-term/tests/integration/helpers.rs b/helix-term/tests/integration/helpers.rs deleted file mode 100644 index 3fe1934f..00000000 --- a/helix-term/tests/integration/helpers.rs +++ /dev/null @@ -1,169 +0,0 @@ -use std::{io::Write, time::Duration}; - -use anyhow::bail; -use crossterm::event::{Event, KeyEvent}; -use helix_core::{test, Selection, Transaction}; -use helix_term::{application::Application, args::Args, config::Config}; -use helix_view::{doc, input::parse_macro}; -use tempfile::NamedTempFile; -use tokio_stream::wrappers::UnboundedReceiverStream; - -#[derive(Clone, Debug)] -pub struct TestCase { - pub in_text: String, - pub in_selection: Selection, - pub in_keys: String, - pub out_text: String, - pub out_selection: Selection, -} - -impl> From<(S, S, S)> for TestCase { - fn from((input, keys, output): (S, S, S)) -> Self { - let (in_text, in_selection) = test::print(&input.into()); - let (out_text, out_selection) = test::print(&output.into()); - - TestCase { - in_text, - in_selection, - in_keys: keys.into(), - out_text, - out_selection, - } - } -} - -#[inline] -pub async fn test_key_sequence( - app: &mut Application, - in_keys: Option<&str>, - test_fn: Option<&dyn Fn(&Application)>, -) -> anyhow::Result<()> { - test_key_sequences(app, vec![(in_keys, test_fn)]).await -} - -pub async fn test_key_sequences( - app: &mut Application, - inputs: Vec<(Option<&str>, Option<&dyn Fn(&Application)>)>, -) -> anyhow::Result<()> { - const TIMEOUT: Duration = Duration::from_millis(500); - let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); - let mut rx_stream = UnboundedReceiverStream::new(rx); - - for (in_keys, test_fn) in inputs { - if let Some(in_keys) = in_keys { - for key_event in parse_macro(&in_keys)?.into_iter() { - tx.send(Ok(Event::Key(KeyEvent::from(key_event))))?; - } - } - - if !app.event_loop_until_idle(&mut rx_stream).await { - bail!("application exited before test function could run"); - } - - if let Some(test) = test_fn { - test(app); - }; - } - - for key_event in parse_macro(":q!")?.into_iter() { - tx.send(Ok(Event::Key(KeyEvent::from(key_event))))?; - } - - let event_loop = app.event_loop(&mut rx_stream); - tokio::time::timeout(TIMEOUT, event_loop).await?; - app.close().await?; - - Ok(()) -} - -pub async fn test_key_sequence_with_input_text>( - app: Option, - test_case: T, - test_fn: &dyn Fn(&Application), -) -> anyhow::Result<()> { - let test_case = test_case.into(); - let mut app = match app { - Some(app) => app, - None => Application::new(Args::default(), Config::default())?, - }; - - let (view, doc) = helix_view::current!(app.editor); - let sel = doc.selection(view.id).clone(); - - // replace the initial text with the input text - doc.apply( - &Transaction::change_by_selection(&doc.text(), &sel, |_| { - (0, doc.text().len_chars(), Some((&test_case.in_text).into())) - }) - .with_selection(test_case.in_selection.clone()), - view.id, - ); - - test_key_sequence(&mut app, Some(&test_case.in_keys), Some(test_fn)).await -} - -/// Use this for very simple test cases where there is one input -/// document, selection, and sequence of key presses, and you just -/// want to verify the resulting document and selection. -pub async fn test_key_sequence_text_result>( - args: Args, - config: Config, - test_case: T, -) -> anyhow::Result<()> { - let test_case = test_case.into(); - let app = Application::new(args, config)?; - - test_key_sequence_with_input_text(Some(app), test_case.clone(), &|app| { - let doc = doc!(app.editor); - assert_eq!(&test_case.out_text, doc.text()); - - let mut selections: Vec<_> = doc.selections().values().cloned().collect(); - assert_eq!(1, selections.len()); - - let sel = selections.pop().unwrap(); - assert_eq!(test_case.out_selection, sel); - }) - .await -} - -pub fn temp_file_with_contents>( - content: S, -) -> anyhow::Result { - let mut temp_file = tempfile::NamedTempFile::new()?; - - temp_file - .as_file_mut() - .write_all(content.as_ref().as_bytes())?; - - temp_file.flush()?; - temp_file.as_file_mut().sync_all()?; - Ok(temp_file) -} - -/// Replaces all LF chars with the system's appropriate line feed -/// character, and if one doesn't exist already, appends the system's -/// appropriate line ending to the end of a string. -pub fn platform_line(input: &str) -> String { - let line_end = helix_core::DEFAULT_LINE_ENDING.as_str(); - - // we can assume that the source files in this code base will always - // be LF, so indoc strings will always insert LF - let mut output = input.replace("\n", line_end); - - if !output.ends_with(line_end) { - output.push_str(line_end); - } - - output -} - -/// Creates a new temporary file that is set to read only. Useful for -/// testing write failures. -pub fn new_readonly_tempfile() -> anyhow::Result { - let mut file = tempfile::NamedTempFile::new()?; - let metadata = file.as_file().metadata()?; - let mut perms = metadata.permissions(); - perms.set_readonly(true); - file.as_file_mut().set_permissions(perms)?; - Ok(file) -} diff --git a/helix-term/tests/integration/movement.rs b/helix-term/tests/integration/movement.rs deleted file mode 100644 index e0bfc3bf..00000000 --- a/helix-term/tests/integration/movement.rs +++ /dev/null @@ -1,135 +0,0 @@ -use helix_term::application::Application; - -use super::*; - -#[tokio::test] -async fn insert_mode_cursor_position() -> anyhow::Result<()> { - test_key_sequence_text_result( - Args::default(), - Config::default(), - TestCase { - in_text: String::new(), - in_selection: Selection::single(0, 0), - in_keys: "i".into(), - out_text: String::new(), - out_selection: Selection::single(0, 0), - }, - ) - .await?; - - test_key_sequence_text_result( - Args::default(), - Config::default(), - ("#[\n|]#", "i", "#[|\n]#"), - ) - .await?; - - test_key_sequence_text_result( - Args::default(), - Config::default(), - ("#[\n|]#", "i", "#[|\n]#"), - ) - .await?; - - test_key_sequence_text_result( - Args::default(), - Config::default(), - ("#[\n|]#", "ii", "#[|\n]#"), - ) - .await?; - - Ok(()) -} - -/// Range direction is preserved when escaping insert mode to normal -#[tokio::test] -async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> { - test_key_sequence_text_result( - Args::default(), - Config::default(), - ("#[f|]#oo\n", "vll", "#[|foo]#\n"), - ) - .await?; - - test_key_sequence_text_result( - Args::default(), - Config::default(), - ( - indoc! {"\ - #[f|]#oo - #(b|)#ar" - }, - "vll", - indoc! {"\ - #[|foo]# - #(|bar)#" - }, - ), - ) - .await?; - - test_key_sequence_text_result( - Args::default(), - Config::default(), - ( - indoc! {"\ - #[f|]#oo - #(b|)#ar" - }, - "a", - indoc! {"\ - #[fo|]#o - #(ba|)#r" - }, - ), - ) - .await?; - - test_key_sequence_text_result( - Args::default(), - Config::default(), - ( - indoc! {"\ - #[f|]#oo - #(b|)#ar" - }, - "a", - indoc! {"\ - #[f|]#oo - #(b|)#ar" - }, - ), - ) - .await?; - - Ok(()) -} - -/// Ensure the very initial cursor in an opened file is the width of -/// the first grapheme -#[tokio::test] -async fn cursor_position_newly_opened_file() -> anyhow::Result<()> { - let test = |content: &str, expected_sel: Selection| -> anyhow::Result<()> { - let file = helpers::temp_file_with_contents(content)?; - - let mut app = Application::new( - Args { - files: vec![(file.path().to_path_buf(), Position::default())], - ..Default::default() - }, - Config::default(), - )?; - - let (view, doc) = helix_view::current!(app.editor); - let sel = doc.selection(view.id).clone(); - assert_eq!(expected_sel, sel); - - Ok(()) - }; - - test("foo", Selection::single(0, 1))?; - test("πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦ foo", Selection::single(0, 7))?; - test("", Selection::single(0, 0))?; - - Ok(()) -} diff --git a/helix-term/tests/integration/write.rs b/helix-term/tests/integration/write.rs deleted file mode 100644 index f3abbd91..00000000 --- a/helix-term/tests/integration/write.rs +++ /dev/null @@ -1,153 +0,0 @@ -use std::{ - io::{Read, Write}, - ops::RangeInclusive, -}; - -use helix_core::diagnostic::Severity; -use helix_term::application::Application; -use helix_view::doc; - -use super::*; - -#[tokio::test] -async fn test_write() -> anyhow::Result<()> { - let mut file = tempfile::NamedTempFile::new()?; - - test_key_sequence( - &mut Application::new( - Args { - files: vec![(file.path().to_path_buf(), Position::default())], - ..Default::default() - }, - Config::default(), - )?, - Some("ii can eat glass, it will not hurt me:w"), - None, - ) - .await?; - - file.as_file_mut().flush()?; - file.as_file_mut().sync_all()?; - - let mut file_content = String::new(); - file.as_file_mut().read_to_string(&mut file_content)?; - assert_eq!( - helpers::platform_line("i can eat glass, it will not hurt me"), - file_content - ); - - Ok(()) -} - -#[tokio::test] -async fn test_write_concurrent() -> anyhow::Result<()> { - let mut file = tempfile::NamedTempFile::new()?; - let mut command = String::new(); - const RANGE: RangeInclusive = 1..=5000; - - for i in RANGE { - let cmd = format!("%c{}:w", i); - command.push_str(&cmd); - } - - test_key_sequence( - &mut Application::new( - Args { - files: vec![(file.path().to_path_buf(), Position::default())], - ..Default::default() - }, - Config::default(), - )?, - Some(&command), - None, - ) - .await?; - - file.as_file_mut().flush()?; - file.as_file_mut().sync_all()?; - - let mut file_content = String::new(); - file.as_file_mut().read_to_string(&mut file_content)?; - assert_eq!(RANGE.end().to_string(), file_content); - - Ok(()) -} - -#[tokio::test] -async fn test_write_fail_mod_flag() -> anyhow::Result<()> { - let file = helpers::new_readonly_tempfile()?; - - test_key_sequences( - &mut Application::new( - Args { - files: vec![(file.path().to_path_buf(), Position::default())], - ..Default::default() - }, - Config::default(), - )?, - vec![ - ( - None, - Some(&|app| { - let doc = doc!(app.editor); - assert!(!doc.is_modified()); - }), - ), - ( - Some("ihello"), - Some(&|app| { - let doc = doc!(app.editor); - assert!(doc.is_modified()); - }), - ), - ( - Some(":w"), - Some(&|app| { - assert_eq!(&Severity::Error, app.editor.get_status().unwrap().1); - - let doc = doc!(app.editor); - assert!(doc.is_modified()); - }), - ), - ], - ) - .await?; - - Ok(()) -} - -#[tokio::test] -async fn test_write_fail_new_path() -> anyhow::Result<()> { - let file = helpers::new_readonly_tempfile()?; - - test_key_sequences( - &mut Application::new(Args::default(), Config::default())?, - vec![ - ( - None, - Some(&|app| { - let doc = doc!(app.editor); - assert_ne!( - Some(&Severity::Error), - app.editor.get_status().map(|status| status.1) - ); - assert_eq!(None, doc.path()); - }), - ), - ( - Some(&format!(":w {}", file.path().to_string_lossy())), - Some(&|app| { - let doc = doc!(app.editor); - assert_eq!( - Some(&Severity::Error), - app.editor.get_status().map(|status| status.1) - ); - assert_eq!(None, doc.path()); - }), - ), - ], - ) - .await?; - - Ok(()) -} diff --git a/helix-term/tests/test/auto_indent.rs b/helix-term/tests/test/auto_indent.rs new file mode 100644 index 00000000..8933cb6a --- /dev/null +++ b/helix-term/tests/test/auto_indent.rs @@ -0,0 +1,26 @@ +use super::*; + +#[tokio::test] +async fn auto_indent_c() -> anyhow::Result<()> { + test_key_sequence_text_result( + Args { + files: vec![(PathBuf::from("foo.c"), Position::default())], + ..Default::default() + }, + Config::default(), + // switches to append mode? + ( + helpers::platform_line("void foo() {#[|}]#").as_ref(), + "i", + helpers::platform_line(indoc! {"\ + void foo() { + #[|\n]#\ + } + "}) + .as_ref(), + ), + ) + .await?; + + Ok(()) +} diff --git a/helix-term/tests/test/auto_pairs.rs b/helix-term/tests/test/auto_pairs.rs new file mode 100644 index 00000000..52fee55e --- /dev/null +++ b/helix-term/tests/test/auto_pairs.rs @@ -0,0 +1,26 @@ +use super::*; + +#[tokio::test] +async fn auto_pairs_basic() -> anyhow::Result<()> { + test_key_sequence_text_result( + Args::default(), + Config::default(), + ("#[\n|]#", "i(", "(#[|)]#\n"), + ) + .await?; + + test_key_sequence_text_result( + Args::default(), + Config { + editor: helix_view::editor::Config { + auto_pairs: AutoPairConfig::Enable(false), + ..Default::default() + }, + ..Default::default() + }, + ("#[\n|]#", "i(", "(#[|\n]#"), + ) + .await?; + + Ok(()) +} diff --git a/helix-term/tests/test/commands.rs b/helix-term/tests/test/commands.rs new file mode 100644 index 00000000..1ff7cc90 --- /dev/null +++ b/helix-term/tests/test/commands.rs @@ -0,0 +1,101 @@ +use std::{ + io::{Read, Write}, + ops::RangeInclusive, +}; + +use helix_core::diagnostic::Severity; +use helix_term::application::Application; + +use super::*; + +#[tokio::test] +async fn test_write_quit_fail() -> anyhow::Result<()> { + let file = helpers::new_readonly_tempfile()?; + + test_key_sequence( + &mut Application::new( + Args { + files: vec![(file.path().to_path_buf(), Position::default())], + ..Default::default() + }, + Config::default(), + )?, + Some("ihello:wq"), + Some(&|app| { + assert_eq!(&Severity::Error, app.editor.get_status().unwrap().1); + }), + ) + .await?; + + Ok(()) +} + +#[tokio::test] +async fn test_buffer_close() -> anyhow::Result<()> { + test_key_sequences( + &mut Application::new(Args::default(), Config::default())?, + vec![ + ( + None, + Some(&|app| { + assert_eq!(1, app.editor.documents().count()); + assert!(!app.editor.is_err()); + }), + ), + ( + Some("ihello:new"), + Some(&|app| { + assert_eq!(2, app.editor.documents().count()); + assert!(!app.editor.is_err()); + }), + ), + ( + Some(":bufferclose"), + Some(&|app| { + assert_eq!(1, app.editor.documents().count()); + assert!(!app.editor.is_err()); + }), + ), + ], + ) + .await?; + + // verify if writes are queued up, it finishes them before closing the buffer + let mut file = tempfile::NamedTempFile::new()?; + let mut command = String::new(); + const RANGE: RangeInclusive = 1..=10; + + for i in RANGE { + let cmd = format!("%c{}:w", i); + command.push_str(&cmd); + } + + command.push_str(":bufferclose"); + + test_key_sequence( + &mut Application::new( + Args { + files: vec![(file.path().to_path_buf(), Position::default())], + ..Default::default() + }, + Config::default(), + )?, + Some(&command), + Some(&|app| { + assert!(!app.editor.is_err(), "error: {:?}", app.editor.get_status()); + + let doc = app.editor.document_by_path(file.path()); + assert!(doc.is_none(), "found doc: {:?}", doc); + }), + ) + .await?; + + file.as_file_mut().flush()?; + file.as_file_mut().sync_all()?; + + let mut file_content = String::new(); + file.as_file_mut().read_to_string(&mut file_content)?; + assert_eq!(RANGE.end().to_string(), file_content); + + Ok(()) +} diff --git a/helix-term/tests/test/helpers.rs b/helix-term/tests/test/helpers.rs new file mode 100644 index 00000000..3fe1934f --- /dev/null +++ b/helix-term/tests/test/helpers.rs @@ -0,0 +1,169 @@ +use std::{io::Write, time::Duration}; + +use anyhow::bail; +use crossterm::event::{Event, KeyEvent}; +use helix_core::{test, Selection, Transaction}; +use helix_term::{application::Application, args::Args, config::Config}; +use helix_view::{doc, input::parse_macro}; +use tempfile::NamedTempFile; +use tokio_stream::wrappers::UnboundedReceiverStream; + +#[derive(Clone, Debug)] +pub struct TestCase { + pub in_text: String, + pub in_selection: Selection, + pub in_keys: String, + pub out_text: String, + pub out_selection: Selection, +} + +impl> From<(S, S, S)> for TestCase { + fn from((input, keys, output): (S, S, S)) -> Self { + let (in_text, in_selection) = test::print(&input.into()); + let (out_text, out_selection) = test::print(&output.into()); + + TestCase { + in_text, + in_selection, + in_keys: keys.into(), + out_text, + out_selection, + } + } +} + +#[inline] +pub async fn test_key_sequence( + app: &mut Application, + in_keys: Option<&str>, + test_fn: Option<&dyn Fn(&Application)>, +) -> anyhow::Result<()> { + test_key_sequences(app, vec![(in_keys, test_fn)]).await +} + +pub async fn test_key_sequences( + app: &mut Application, + inputs: Vec<(Option<&str>, Option<&dyn Fn(&Application)>)>, +) -> anyhow::Result<()> { + const TIMEOUT: Duration = Duration::from_millis(500); + let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); + let mut rx_stream = UnboundedReceiverStream::new(rx); + + for (in_keys, test_fn) in inputs { + if let Some(in_keys) = in_keys { + for key_event in parse_macro(&in_keys)?.into_iter() { + tx.send(Ok(Event::Key(KeyEvent::from(key_event))))?; + } + } + + if !app.event_loop_until_idle(&mut rx_stream).await { + bail!("application exited before test function could run"); + } + + if let Some(test) = test_fn { + test(app); + }; + } + + for key_event in parse_macro(":q!")?.into_iter() { + tx.send(Ok(Event::Key(KeyEvent::from(key_event))))?; + } + + let event_loop = app.event_loop(&mut rx_stream); + tokio::time::timeout(TIMEOUT, event_loop).await?; + app.close().await?; + + Ok(()) +} + +pub async fn test_key_sequence_with_input_text>( + app: Option, + test_case: T, + test_fn: &dyn Fn(&Application), +) -> anyhow::Result<()> { + let test_case = test_case.into(); + let mut app = match app { + Some(app) => app, + None => Application::new(Args::default(), Config::default())?, + }; + + let (view, doc) = helix_view::current!(app.editor); + let sel = doc.selection(view.id).clone(); + + // replace the initial text with the input text + doc.apply( + &Transaction::change_by_selection(&doc.text(), &sel, |_| { + (0, doc.text().len_chars(), Some((&test_case.in_text).into())) + }) + .with_selection(test_case.in_selection.clone()), + view.id, + ); + + test_key_sequence(&mut app, Some(&test_case.in_keys), Some(test_fn)).await +} + +/// Use this for very simple test cases where there is one input +/// document, selection, and sequence of key presses, and you just +/// want to verify the resulting document and selection. +pub async fn test_key_sequence_text_result>( + args: Args, + config: Config, + test_case: T, +) -> anyhow::Result<()> { + let test_case = test_case.into(); + let app = Application::new(args, config)?; + + test_key_sequence_with_input_text(Some(app), test_case.clone(), &|app| { + let doc = doc!(app.editor); + assert_eq!(&test_case.out_text, doc.text()); + + let mut selections: Vec<_> = doc.selections().values().cloned().collect(); + assert_eq!(1, selections.len()); + + let sel = selections.pop().unwrap(); + assert_eq!(test_case.out_selection, sel); + }) + .await +} + +pub fn temp_file_with_contents>( + content: S, +) -> anyhow::Result { + let mut temp_file = tempfile::NamedTempFile::new()?; + + temp_file + .as_file_mut() + .write_all(content.as_ref().as_bytes())?; + + temp_file.flush()?; + temp_file.as_file_mut().sync_all()?; + Ok(temp_file) +} + +/// Replaces all LF chars with the system's appropriate line feed +/// character, and if one doesn't exist already, appends the system's +/// appropriate line ending to the end of a string. +pub fn platform_line(input: &str) -> String { + let line_end = helix_core::DEFAULT_LINE_ENDING.as_str(); + + // we can assume that the source files in this code base will always + // be LF, so indoc strings will always insert LF + let mut output = input.replace("\n", line_end); + + if !output.ends_with(line_end) { + output.push_str(line_end); + } + + output +} + +/// Creates a new temporary file that is set to read only. Useful for +/// testing write failures. +pub fn new_readonly_tempfile() -> anyhow::Result { + let mut file = tempfile::NamedTempFile::new()?; + let metadata = file.as_file().metadata()?; + let mut perms = metadata.permissions(); + perms.set_readonly(true); + file.as_file_mut().set_permissions(perms)?; + Ok(file) +} diff --git a/helix-term/tests/test/movement.rs b/helix-term/tests/test/movement.rs new file mode 100644 index 00000000..e0bfc3bf --- /dev/null +++ b/helix-term/tests/test/movement.rs @@ -0,0 +1,135 @@ +use helix_term::application::Application; + +use super::*; + +#[tokio::test] +async fn insert_mode_cursor_position() -> anyhow::Result<()> { + test_key_sequence_text_result( + Args::default(), + Config::default(), + TestCase { + in_text: String::new(), + in_selection: Selection::single(0, 0), + in_keys: "i".into(), + out_text: String::new(), + out_selection: Selection::single(0, 0), + }, + ) + .await?; + + test_key_sequence_text_result( + Args::default(), + Config::default(), + ("#[\n|]#", "i", "#[|\n]#"), + ) + .await?; + + test_key_sequence_text_result( + Args::default(), + Config::default(), + ("#[\n|]#", "i", "#[|\n]#"), + ) + .await?; + + test_key_sequence_text_result( + Args::default(), + Config::default(), + ("#[\n|]#", "ii", "#[|\n]#"), + ) + .await?; + + Ok(()) +} + +/// Range direction is preserved when escaping insert mode to normal +#[tokio::test] +async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> { + test_key_sequence_text_result( + Args::default(), + Config::default(), + ("#[f|]#oo\n", "vll", "#[|foo]#\n"), + ) + .await?; + + test_key_sequence_text_result( + Args::default(), + Config::default(), + ( + indoc! {"\ + #[f|]#oo + #(b|)#ar" + }, + "vll", + indoc! {"\ + #[|foo]# + #(|bar)#" + }, + ), + ) + .await?; + + test_key_sequence_text_result( + Args::default(), + Config::default(), + ( + indoc! {"\ + #[f|]#oo + #(b|)#ar" + }, + "a", + indoc! {"\ + #[fo|]#o + #(ba|)#r" + }, + ), + ) + .await?; + + test_key_sequence_text_result( + Args::default(), + Config::default(), + ( + indoc! {"\ + #[f|]#oo + #(b|)#ar" + }, + "a", + indoc! {"\ + #[f|]#oo + #(b|)#ar" + }, + ), + ) + .await?; + + Ok(()) +} + +/// Ensure the very initial cursor in an opened file is the width of +/// the first grapheme +#[tokio::test] +async fn cursor_position_newly_opened_file() -> anyhow::Result<()> { + let test = |content: &str, expected_sel: Selection| -> anyhow::Result<()> { + let file = helpers::temp_file_with_contents(content)?; + + let mut app = Application::new( + Args { + files: vec![(file.path().to_path_buf(), Position::default())], + ..Default::default() + }, + Config::default(), + )?; + + let (view, doc) = helix_view::current!(app.editor); + let sel = doc.selection(view.id).clone(); + assert_eq!(expected_sel, sel); + + Ok(()) + }; + + test("foo", Selection::single(0, 1))?; + test("πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦ foo", Selection::single(0, 7))?; + test("", Selection::single(0, 0))?; + + Ok(()) +} diff --git a/helix-term/tests/test/write.rs b/helix-term/tests/test/write.rs new file mode 100644 index 00000000..f3abbd91 --- /dev/null +++ b/helix-term/tests/test/write.rs @@ -0,0 +1,153 @@ +use std::{ + io::{Read, Write}, + ops::RangeInclusive, +}; + +use helix_core::diagnostic::Severity; +use helix_term::application::Application; +use helix_view::doc; + +use super::*; + +#[tokio::test] +async fn test_write() -> anyhow::Result<()> { + let mut file = tempfile::NamedTempFile::new()?; + + test_key_sequence( + &mut Application::new( + Args { + files: vec![(file.path().to_path_buf(), Position::default())], + ..Default::default() + }, + Config::default(), + )?, + Some("ii can eat glass, it will not hurt me:w"), + None, + ) + .await?; + + file.as_file_mut().flush()?; + file.as_file_mut().sync_all()?; + + let mut file_content = String::new(); + file.as_file_mut().read_to_string(&mut file_content)?; + assert_eq!( + helpers::platform_line("i can eat glass, it will not hurt me"), + file_content + ); + + Ok(()) +} + +#[tokio::test] +async fn test_write_concurrent() -> anyhow::Result<()> { + let mut file = tempfile::NamedTempFile::new()?; + let mut command = String::new(); + const RANGE: RangeInclusive = 1..=5000; + + for i in RANGE { + let cmd = format!("%c{}:w", i); + command.push_str(&cmd); + } + + test_key_sequence( + &mut Application::new( + Args { + files: vec![(file.path().to_path_buf(), Position::default())], + ..Default::default() + }, + Config::default(), + )?, + Some(&command), + None, + ) + .await?; + + file.as_file_mut().flush()?; + file.as_file_mut().sync_all()?; + + let mut file_content = String::new(); + file.as_file_mut().read_to_string(&mut file_content)?; + assert_eq!(RANGE.end().to_string(), file_content); + + Ok(()) +} + +#[tokio::test] +async fn test_write_fail_mod_flag() -> anyhow::Result<()> { + let file = helpers::new_readonly_tempfile()?; + + test_key_sequences( + &mut Application::new( + Args { + files: vec![(file.path().to_path_buf(), Position::default())], + ..Default::default() + }, + Config::default(), + )?, + vec![ + ( + None, + Some(&|app| { + let doc = doc!(app.editor); + assert!(!doc.is_modified()); + }), + ), + ( + Some("ihello"), + Some(&|app| { + let doc = doc!(app.editor); + assert!(doc.is_modified()); + }), + ), + ( + Some(":w"), + Some(&|app| { + assert_eq!(&Severity::Error, app.editor.get_status().unwrap().1); + + let doc = doc!(app.editor); + assert!(doc.is_modified()); + }), + ), + ], + ) + .await?; + + Ok(()) +} + +#[tokio::test] +async fn test_write_fail_new_path() -> anyhow::Result<()> { + let file = helpers::new_readonly_tempfile()?; + + test_key_sequences( + &mut Application::new(Args::default(), Config::default())?, + vec![ + ( + None, + Some(&|app| { + let doc = doc!(app.editor); + assert_ne!( + Some(&Severity::Error), + app.editor.get_status().map(|status| status.1) + ); + assert_eq!(None, doc.path()); + }), + ), + ( + Some(&format!(":w {}", file.path().to_string_lossy())), + Some(&|app| { + let doc = doc!(app.editor); + assert_eq!( + Some(&Severity::Error), + app.editor.get_status().map(|status| status.1) + ); + assert_eq!(None, doc.path()); + }), + ), + ], + ) + .await?; + + Ok(()) +} -- cgit v1.2.3-70-g09d2 From 7c0bca186cdacf070355c1a4ab82121d6a4d2e27 Mon Sep 17 00:00:00 2001 From: Skyler Hawthorne Date: Sun, 22 May 2022 13:29:52 -0400 Subject: rename test helpers --- helix-term/tests/integration.rs | 8 +-- helix-term/tests/test/auto_indent.rs | 2 +- helix-term/tests/test/auto_pairs.rs | 9 +-- helix-term/tests/test/helpers.rs | 13 +++-- helix-term/tests/test/movement.rs | 103 +++++++++++------------------------ 5 files changed, 45 insertions(+), 90 deletions(-) (limited to 'helix-term/tests/integration.rs') diff --git a/helix-term/tests/integration.rs b/helix-term/tests/integration.rs index 376bc88b..11bc4e4c 100644 --- a/helix-term/tests/integration.rs +++ b/helix-term/tests/integration.rs @@ -13,13 +13,7 @@ mod test { #[tokio::test] async fn hello_world() -> anyhow::Result<()> { - test_key_sequence_text_result( - Args::default(), - Config::default(), - ("#[\n|]#", "ihello world", "hello world#[|\n]#"), - ) - .await?; - + test(("#[\n|]#", "ihello world", "hello world#[|\n]#")).await?; Ok(()) } diff --git a/helix-term/tests/test/auto_indent.rs b/helix-term/tests/test/auto_indent.rs index 8933cb6a..2f638893 100644 --- a/helix-term/tests/test/auto_indent.rs +++ b/helix-term/tests/test/auto_indent.rs @@ -2,7 +2,7 @@ use super::*; #[tokio::test] async fn auto_indent_c() -> anyhow::Result<()> { - test_key_sequence_text_result( + test_with_config( Args { files: vec![(PathBuf::from("foo.c"), Position::default())], ..Default::default() diff --git a/helix-term/tests/test/auto_pairs.rs b/helix-term/tests/test/auto_pairs.rs index 52fee55e..ec47a5b4 100644 --- a/helix-term/tests/test/auto_pairs.rs +++ b/helix-term/tests/test/auto_pairs.rs @@ -2,14 +2,9 @@ use super::*; #[tokio::test] async fn auto_pairs_basic() -> anyhow::Result<()> { - test_key_sequence_text_result( - Args::default(), - Config::default(), - ("#[\n|]#", "i(", "(#[|)]#\n"), - ) - .await?; + test(("#[\n|]#", "i(", "(#[|)]#\n")).await?; - test_key_sequence_text_result( + test_with_config( Args::default(), Config { editor: helix_view::editor::Config { diff --git a/helix-term/tests/test/helpers.rs b/helix-term/tests/test/helpers.rs index 3fe1934f..2bebe31b 100644 --- a/helix-term/tests/test/helpers.rs +++ b/helix-term/tests/test/helpers.rs @@ -41,6 +41,7 @@ pub async fn test_key_sequence( test_key_sequences(app, vec![(in_keys, test_fn)]).await } +#[allow(clippy::type_complexity)] pub async fn test_key_sequences( app: &mut Application, inputs: Vec<(Option<&str>, Option<&dyn Fn(&Application)>)>, @@ -51,7 +52,7 @@ pub async fn test_key_sequences( for (in_keys, test_fn) in inputs { if let Some(in_keys) = in_keys { - for key_event in parse_macro(&in_keys)?.into_iter() { + for key_event in parse_macro(in_keys)?.into_iter() { tx.send(Ok(Event::Key(KeyEvent::from(key_event))))?; } } @@ -92,7 +93,7 @@ pub async fn test_key_sequence_with_input_text>( // replace the initial text with the input text doc.apply( - &Transaction::change_by_selection(&doc.text(), &sel, |_| { + &Transaction::change_by_selection(doc.text(), &sel, |_| { (0, doc.text().len_chars(), Some((&test_case.in_text).into())) }) .with_selection(test_case.in_selection.clone()), @@ -105,7 +106,7 @@ pub async fn test_key_sequence_with_input_text>( /// Use this for very simple test cases where there is one input /// document, selection, and sequence of key presses, and you just /// want to verify the resulting document and selection. -pub async fn test_key_sequence_text_result>( +pub async fn test_with_config>( args: Args, config: Config, test_case: T, @@ -126,6 +127,10 @@ pub async fn test_key_sequence_text_result>( .await } +pub async fn test>(test_case: T) -> anyhow::Result<()> { + test_with_config(Args::default(), Config::default(), test_case).await +} + pub fn temp_file_with_contents>( content: S, ) -> anyhow::Result { @@ -148,7 +153,7 @@ pub fn platform_line(input: &str) -> String { // we can assume that the source files in this code base will always // be LF, so indoc strings will always insert LF - let mut output = input.replace("\n", line_end); + let mut output = input.replace('\n', line_end); if !output.ends_with(line_end) { output.push_str(line_end); diff --git a/helix-term/tests/test/movement.rs b/helix-term/tests/test/movement.rs index e0bfc3bf..5fb2ce25 100644 --- a/helix-term/tests/test/movement.rs +++ b/helix-term/tests/test/movement.rs @@ -4,39 +4,18 @@ use super::*; #[tokio::test] async fn insert_mode_cursor_position() -> anyhow::Result<()> { - test_key_sequence_text_result( - Args::default(), - Config::default(), - TestCase { - in_text: String::new(), - in_selection: Selection::single(0, 0), - in_keys: "i".into(), - out_text: String::new(), - out_selection: Selection::single(0, 0), - }, - ) - .await?; - - test_key_sequence_text_result( - Args::default(), - Config::default(), - ("#[\n|]#", "i", "#[|\n]#"), - ) - .await?; - - test_key_sequence_text_result( - Args::default(), - Config::default(), - ("#[\n|]#", "i", "#[|\n]#"), - ) + test(TestCase { + in_text: String::new(), + in_selection: Selection::single(0, 0), + in_keys: "i".into(), + out_text: String::new(), + out_selection: Selection::single(0, 0), + }) .await?; - test_key_sequence_text_result( - Args::default(), - Config::default(), - ("#[\n|]#", "ii", "#[|\n]#"), - ) - .await?; + test(("#[\n|]#", "i", "#[|\n]#")).await?; + test(("#[\n|]#", "i", "#[|\n]#")).await?; + test(("#[\n|]#", "ii", "#[|\n]#")).await?; Ok(()) } @@ -44,62 +23,44 @@ async fn insert_mode_cursor_position() -> anyhow::Result<()> { /// Range direction is preserved when escaping insert mode to normal #[tokio::test] async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> { - test_key_sequence_text_result( - Args::default(), - Config::default(), - ("#[f|]#oo\n", "vll", "#[|foo]#\n"), - ) - .await?; - - test_key_sequence_text_result( - Args::default(), - Config::default(), - ( - indoc! {"\ + test(("#[f|]#oo\n", "vll", "#[|foo]#\n")).await?; + test(( + indoc! {"\ #[f|]#oo #(b|)#ar" - }, - "vll", - indoc! {"\ + }, + "vll", + indoc! {"\ #[|foo]# #(|bar)#" - }, - ), - ) + }, + )) .await?; - test_key_sequence_text_result( - Args::default(), - Config::default(), - ( - indoc! {"\ + test(( + indoc! {"\ #[f|]#oo #(b|)#ar" - }, - "a", - indoc! {"\ + }, + "a", + indoc! {"\ #[fo|]#o #(ba|)#r" - }, - ), - ) + }, + )) .await?; - test_key_sequence_text_result( - Args::default(), - Config::default(), - ( - indoc! {"\ + test(( + indoc! {"\ #[f|]#oo #(b|)#ar" - }, - "a", - indoc! {"\ + }, + "a", + indoc! {"\ #[f|]#oo #(b|)#ar" - }, - ), - ) + }, + )) .await?; Ok(()) -- cgit v1.2.3-70-g09d2