From 3ddfb4d5949eb687605b1b27246a69ba8a7e2ba8 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 24 Apr 2021 14:47:52 -0700 Subject: [PATCH] Implement editor-related steps in pytest-bdd - Implement mock editor fixture - Add fixture to keep track of editor state - Implement various steps to check editor state Co-authored-by: Micah Jerome Ellison --- jrnl/editor.py | 2 +- poetry.lock | 287 ++++++++++++++++++++++++++++--- pyproject.toml | 9 +- tests/features/write.feature | 175 ++++++++++--------- tests/step_defs/conftest.py | 119 ++++++++++++- tests/step_defs/test_features.py | 16 +- 6 files changed, 475 insertions(+), 133 deletions(-) diff --git a/jrnl/editor.py b/jrnl/editor.py index 086d84db..7c7413e8 100644 --- a/jrnl/editor.py +++ b/jrnl/editor.py @@ -26,7 +26,7 @@ def get_text_from_editor(config, template=""): try: subprocess.call(split_args(config["editor"]) + [tmpfile]) - except Exception as e: + except FileNotFoundError as e: error_msg = f""" {ERROR_COLOR}{str(e)}{RESET_COLOR} diff --git a/poetry.lock b/poetry.lock index 34184977..65670545 100644 --- a/poetry.lock +++ b/poetry.lock @@ -17,6 +17,14 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "appnope" +version = "0.1.2" +description = "Disable App Nap on macOS >= 10.9" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "argcomplete" version = "1.12.3" @@ -61,6 +69,14 @@ docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] +[[package]] +name = "backcall" +version = "0.2.0" +description = "Specifications for callback functions passed in to an API" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "behave" version = "1.2.6" @@ -152,6 +168,14 @@ sdist = ["setuptools-rust (>=0.11.4)"] ssh = ["bcrypt (>=3.1.5)"] test = ["pytest (>=6.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] +[[package]] +name = "decorator" +version = "5.0.9" +description = "Decorators for Humans" +category = "dev" +optional = false +python-versions = ">=3.5" + [[package]] name = "ghp-import" version = "2.0.1" @@ -174,14 +198,6 @@ category = "dev" optional = false python-versions = "*" -[[package]] -name = "icdiff" -version = "1.9.1" -description = "improved colored diff" -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "importlib-metadata" version = "4.5.0" @@ -206,6 +222,74 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "ipdb" +version = "0.13.9" +description = "IPython-enabled pdb" +category = "dev" +optional = false +python-versions = ">=2.7" + +[package.dependencies] +decorator = {version = "*", markers = "python_version > \"3.6\""} +ipython = {version = ">=7.17.0", markers = "python_version > \"3.6\""} +toml = {version = ">=0.10.2", markers = "python_version > \"3.6\""} + +[[package]] +name = "ipython" +version = "7.24.1" +description = "IPython: Productive Interactive Computing" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +appnope = {version = "*", markers = "sys_platform == \"darwin\""} +backcall = "*" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +decorator = "*" +jedi = ">=0.16" +matplotlib-inline = "*" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} +pickleshare = "*" +prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" +pygments = "*" +traitlets = ">=4.2" + +[package.extras] +all = ["Sphinx (>=1.3)", "ipykernel", "ipyparallel", "ipywidgets", "nbconvert", "nbformat", "nose (>=0.10.1)", "notebook", "numpy (>=1.17)", "pygments", "qtconsole", "requests", "testpath"] +doc = ["Sphinx (>=1.3)"] +kernel = ["ipykernel"] +nbconvert = ["nbconvert"] +nbformat = ["nbformat"] +notebook = ["notebook", "ipywidgets"] +parallel = ["ipyparallel"] +qtconsole = ["qtconsole"] +test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.17)"] + +[[package]] +name = "ipython-genutils" +version = "0.2.0" +description = "Vestigial utilities from IPython" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "jedi" +version = "0.18.0" +description = "An autocompletion tool for Python that can be used for text editors." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +parso = ">=0.8.0,<0.9.0" + +[package.extras] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] +testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<6.0.0)"] + [[package]] name = "jeepney" version = "0.6.0" @@ -286,6 +370,17 @@ category = "dev" optional = false python-versions = ">=3.6" +[[package]] +name = "matplotlib-inline" +version = "0.1.2" +description = "Inline Matplotlib backend for Jupyter" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +traitlets = "*" + [[package]] name = "mergedeep" version = "1.3.4" @@ -368,6 +463,18 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "parso" +version = "0.8.2" +description = "A Python Parser" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] +testing = ["docopt", "pytest (<6.0.0)"] + [[package]] name = "pathspec" version = "0.8.1" @@ -376,6 +483,25 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +[[package]] +name = "pexpect" +version = "4.8.0" +description = "Pexpect allows easy control of interactive console applications." +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +ptyprocess = ">=0.5" + +[[package]] +name = "pickleshare" +version = "0.7.5" +description = "Tiny 'shelve'-like database with concurrency support" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "pluggy" version = "0.13.1" @@ -391,9 +517,20 @@ importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} dev = ["pre-commit", "tox"] [[package]] -name = "pprintpp" -version = "0.4.0" -description = "A drop-in replacement for pprint that's actually pretty" +name = "prompt-toolkit" +version = "3.0.19" +description = "Library for building powerful interactive command lines in Python" +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[package.dependencies] +wcwidth = "*" + +[[package]] +name = "ptyprocess" +version = "0.7.0" +description = "Run a subprocess in a pseudo terminal" category = "dev" optional = false python-versions = "*" @@ -422,6 +559,14 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +[[package]] +name = "pygments" +version = "2.9.0" +description = "Pygments is a syntax highlighting package written in Python." +category = "dev" +optional = false +python-versions = ">=3.5" + [[package]] name = "pyparsing" version = "2.4.7" @@ -470,17 +615,16 @@ pytest = ">=4.3" six = ">=1.9.0" [[package]] -name = "pytest-icdiff" -version = "0.5" -description = "use icdiff for better error messages in pytest assertions" +name = "pytest-clarity" +version = "0.3.0a0" +description = "A plugin providing an alternative, colourful diff output for failing assertions." category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] -icdiff = "*" -pprintpp = "*" -pytest = "*" +pytest = ">=3.5.0" +termcolor = "1.1.0" [[package]] name = "python-dateutil" @@ -564,6 +708,14 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +[[package]] +name = "termcolor" +version = "1.1.0" +description = "ANSII Color formatting for output in terminal." +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "textwrap3" version = "0.9.2" @@ -580,6 +732,20 @@ category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +[[package]] +name = "traitlets" +version = "5.0.5" +description = "Traitlets Python configuration system" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +ipython-genutils = "*" + +[package.extras] +test = ["pytest"] + [[package]] name = "typed-ast" version = "1.4.3" @@ -618,6 +784,14 @@ python-versions = ">=3.6" [package.extras] watchmedo = ["PyYAML (>=3.10)", "argh (>=0.24.1)"] +[[package]] +name = "wcwidth" +version = "0.2.5" +description = "Measures the displayed width of unicode strings in a terminal" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "xmltodict" version = "0.12.0" @@ -658,7 +832,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pyt [metadata] lock-version = "1.1" python-versions = ">=3.7.0, <3.10" -content-hash = "a0d3f3e30dbe80528547b49ae7bb39c6c1a911e2552d8b10d409daf81cdb28d1" +content-hash = "d9a47064f2050860c955a0871b2bd8899c2f24aaf6482f6a742316fd1fd95ba3" [metadata.files] ansiwrap = [ @@ -669,6 +843,10 @@ appdirs = [ {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, ] +appnope = [ + {file = "appnope-0.1.2-py2.py3-none-any.whl", hash = "sha256:93aa393e9d6c54c5cd570ccadd8edad61ea0c4b9ea7a01409020c9aa019eb442"}, + {file = "appnope-0.1.2.tar.gz", hash = "sha256:dd83cd4b5b460958838f6eb3000c660b1f9caf2a5b1de4264e941512f603258a"}, +] argcomplete = [ {file = "argcomplete-1.12.3-py2.py3-none-any.whl", hash = "sha256:291f0beca7fd49ce285d2f10e4c1c77e9460cf823eef2de54df0c0fec88b0d81"}, {file = "argcomplete-1.12.3.tar.gz", hash = "sha256:2c7dbffd8c045ea534921e63b0be6fe65e88599990d8dc408ac8c542b72a5445"}, @@ -684,6 +862,10 @@ attrs = [ {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, ] +backcall = [ + {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, + {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, +] behave = [ {file = "behave-1.2.6-py2.py3-none-any.whl", hash = "sha256:ebda1a6c9e5bfe95c5f9f0a2794e01c7098b3dde86c10a95d8621c5907ff6f1c"}, {file = "behave-1.2.6.tar.gz", hash = "sha256:b9662327aa53294c1351b0a9c369093ccec1d21026f050c3bd9b3e5cccf81a86"}, @@ -753,15 +935,16 @@ cryptography = [ {file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2014_x86_64.whl", hash = "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9"}, {file = "cryptography-3.4.7.tar.gz", hash = "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713"}, ] +decorator = [ + {file = "decorator-5.0.9-py3-none-any.whl", hash = "sha256:6e5c199c16f7a9f0e3a61a4a54b3d27e7dad0dbdde92b944426cb20914376323"}, + {file = "decorator-5.0.9.tar.gz", hash = "sha256:72ecfba4320a893c53f9706bebb2d55c270c1e51a28789361aa93e4a21319ed5"}, +] ghp-import = [ {file = "ghp-import-2.0.1.tar.gz", hash = "sha256:753de2eace6e0f7d4edfb3cce5e3c3b98cd52aadb80163303d1d036bda7b4483"}, ] glob2 = [ {file = "glob2-0.7.tar.gz", hash = "sha256:85c3dbd07c8aa26d63d7aacee34fa86e9a91a3873bc30bf62ec46e531f92ab8c"}, ] -icdiff = [ - {file = "icdiff-1.9.1.tar.gz", hash = "sha256:66972dd03318da55280991db375d3ef6b66d948c67af96c1ebdb21587e86655e"}, -] importlib-metadata = [ {file = "importlib_metadata-4.5.0-py3-none-any.whl", hash = "sha256:833b26fb89d5de469b24a390e9df088d4e52e4ba33b01dc5e0e4f41b81a16c00"}, {file = "importlib_metadata-4.5.0.tar.gz", hash = "sha256:b142cc1dd1342f31ff04bb7d022492b09920cb64fed867cd3ea6f80fe3ebd139"}, @@ -770,6 +953,21 @@ iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] +ipdb = [ + {file = "ipdb-0.13.9.tar.gz", hash = "sha256:951bd9a64731c444fd907a5ce268543020086a697f6be08f7cc2c9a752a278c5"}, +] +ipython = [ + {file = "ipython-7.24.1-py3-none-any.whl", hash = "sha256:d513e93327cf8657d6467c81f1f894adc125334ffe0e4ddd1abbb1c78d828703"}, + {file = "ipython-7.24.1.tar.gz", hash = "sha256:9bc24a99f5d19721fb8a2d1408908e9c0520a17fff2233ffe82620847f17f1b6"}, +] +ipython-genutils = [ + {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"}, + {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"}, +] +jedi = [ + {file = "jedi-0.18.0-py2.py3-none-any.whl", hash = "sha256:18456d83f65f400ab0c2d3319e48520420ef43b23a086fdc05dff34132f0fb93"}, + {file = "jedi-0.18.0.tar.gz", hash = "sha256:92550a404bad8afed881a137ec9a461fed49eca661414be45059329614ed0707"}, +] jeepney = [ {file = "jeepney-0.6.0-py3-none-any.whl", hash = "sha256:aec56c0eb1691a841795111e184e13cad504f7703b9a64f63020816afa79a8ae"}, {file = "jeepney-0.6.0.tar.gz", hash = "sha256:7d59b6622675ca9e993a6bd38de845051d315f8b0c72cca3aef733a20b648657"}, @@ -825,6 +1023,10 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, ] +matplotlib-inline = [ + {file = "matplotlib-inline-0.1.2.tar.gz", hash = "sha256:f41d5ff73c9f5385775d5c0bc13b424535c8402fe70ea8210f93e11f3683993e"}, + {file = "matplotlib_inline-0.1.2-py3-none-any.whl", hash = "sha256:5cf1176f554abb4fa98cb362aa2b55c500147e4bdbb07e3fda359143e1da0811"}, +] mergedeep = [ {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, @@ -852,17 +1054,33 @@ parsedatetime = [ {file = "parsedatetime-2.6-py3-none-any.whl", hash = "sha256:cb96edd7016872f58479e35879294258c71437195760746faffedb692aef000b"}, {file = "parsedatetime-2.6.tar.gz", hash = "sha256:4cb368fbb18a0b7231f4d76119165451c8d2e35951455dfee97c62a87b04d455"}, ] +parso = [ + {file = "parso-0.8.2-py2.py3-none-any.whl", hash = "sha256:a8c4922db71e4fdb90e0d0bc6e50f9b273d3397925e5e60a717e719201778d22"}, + {file = "parso-0.8.2.tar.gz", hash = "sha256:12b83492c6239ce32ff5eed6d3639d6a536170723c6f3f1506869f1ace413398"}, +] pathspec = [ {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, ] +pexpect = [ + {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, + {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, +] +pickleshare = [ + {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, + {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, +] pluggy = [ {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, ] -pprintpp = [ - {file = "pprintpp-0.4.0-py2.py3-none-any.whl", hash = "sha256:b6b4dcdd0c0c0d75e4d7b2f21a9e933e5b2ce62b26e1a54537f9651ae5a5c01d"}, - {file = "pprintpp-0.4.0.tar.gz", hash = "sha256:ea826108e2c7f49dc6d66c752973c3fc9749142a798d6b254e1e301cfdbc6403"}, +prompt-toolkit = [ + {file = "prompt_toolkit-3.0.19-py3-none-any.whl", hash = "sha256:7089d8d2938043508aa9420ec18ce0922885304cddae87fb96eebca942299f88"}, + {file = "prompt_toolkit-3.0.19.tar.gz", hash = "sha256:08360ee3a3148bdb5163621709ee322ec34fc4375099afa4bbf751e9b7b7fa4f"}, +] +ptyprocess = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, ] py = [ {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, @@ -876,6 +1094,10 @@ pyflakes = [ {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, ] +pygments = [ + {file = "Pygments-2.9.0-py3-none-any.whl", hash = "sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e"}, + {file = "Pygments-2.9.0.tar.gz", hash = "sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f"}, +] pyparsing = [ {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, @@ -888,8 +1110,8 @@ pytest-bdd = [ {file = "pytest-bdd-4.0.2.tar.gz", hash = "sha256:982489f2f036c7561affe4eeb5b392a37e1ace2a9f260cad747b1c8119e63cfd"}, {file = "pytest_bdd-4.0.2-py2.py3-none-any.whl", hash = "sha256:74ea5a147ea558c99ae83d838e6acbe5c9e6843884a958f8231615d96838733d"}, ] -pytest-icdiff = [ - {file = "pytest-icdiff-0.5.tar.gz", hash = "sha256:3a14097f4385665cb04330e6ae09a3dd430375f717e94482af6944470ad5f100"}, +pytest-clarity = [ + {file = "pytest-clarity-0.3.0a0.tar.gz", hash = "sha256:5cc99e3d9b7969dfe17e5f6072d45a917c59d363b679686d3c958a1ded2e4dcf"}, ] python-dateutil = [ {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"}, @@ -985,6 +1207,9 @@ six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] +termcolor = [ + {file = "termcolor-1.1.0.tar.gz", hash = "sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b"}, +] textwrap3 = [ {file = "textwrap3-0.9.2-py2.py3-none-any.whl", hash = "sha256:bf5f4c40faf2a9ff00a9e0791fed5da7415481054cef45bb4a3cfb1f69044ae0"}, {file = "textwrap3-0.9.2.zip", hash = "sha256:5008eeebdb236f6303dcd68f18b856d355f6197511d952ba74bc75e40e0c3414"}, @@ -993,6 +1218,10 @@ toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] +traitlets = [ + {file = "traitlets-5.0.5-py3-none-any.whl", hash = "sha256:69ff3f9d5351f31a7ad80443c2674b7099df13cc41fc5fa6e2f6d3b0330b0426"}, + {file = "traitlets-5.0.5.tar.gz", hash = "sha256:178f4ce988f69189f7e523337a3e11d91c786ded9360174a3d9ca83e79bc5396"}, +] typed-ast = [ {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, @@ -1053,6 +1282,10 @@ watchdog = [ {file = "watchdog-2.1.2-py3-none-win_ia64.whl", hash = "sha256:104266a778906ae0e971368d368a65c4cd032a490a9fca5ba0b78c6c7ae11720"}, {file = "watchdog-2.1.2.tar.gz", hash = "sha256:0237db4d9024859bea27d0efb59fe75eef290833fd988b8ead7a879b0308c2db"}, ] +wcwidth = [ + {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, + {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, +] xmltodict = [ {file = "xmltodict-0.12.0-py2.py3-none-any.whl", hash = "sha256:8bbcb45cc982f48b2ca8fe7e7827c5d792f217ecf1792626f808bf41c3b86051"}, {file = "xmltodict-0.12.0.tar.gz", hash = "sha256:50d8c638ed7ecb88d90561beedbf720c9b4e851a9fa6c47ebd64e99d166d8a21"}, diff --git a/pyproject.toml b/pyproject.toml index eac62079..2719b954 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,8 +51,9 @@ toml = ">=0.10" pyflakes = ">=2.2.0" pytest = ">=6.2" pytest-bdd = "^4.0.1" -pytest-icdiff = "^0.5" yq = ">=2.11" +ipdb = ">=0.13" +pytest-clarity = "^0.3.0-alpha.0" [tool.poetry.scripts] jrnl = 'jrnl.cli:cli' @@ -66,9 +67,15 @@ force_sort_within_sections = true [tool.pytest.ini_options] minversion = "6.0" +required_plugins = [ + "pytest-bdd" +] markers = [ "todo", ] +addopts = [ + "--pdbcls=IPython.terminal.debugger:Pdb" +] filterwarnings = [ "ignore::DeprecationWarning", diff --git a/tests/features/write.feature b/tests/features/write.feature index eb22e480..aec325d5 100644 --- a/tests/features/write.feature +++ b/tests/features/write.feature @@ -1,49 +1,49 @@ Feature: Writing new entries. Scenario Outline: Multiline entry with punctuation should keep title punctuation - Given we use the config ".yaml" + Given we use the config "" And we use the password "bad doggie no biscuit" if prompted When we run "jrnl This is. the title\\n This is the second line" And we run "jrnl -n 1" Then the output should contain "This is. the title" Examples: configs - | config_file | - | simple | - | empty_folder | - | dayone | - | encrypted | + | config_file | + | simple.yaml | + | empty_folder.yaml | + | dayone.yaml | + | encrypted.yaml | Scenario Outline: Single line entry with period should be split at period - Given we use the config ".yaml" + Given we use the config "" And we use the password "test" if prompted When we run "jrnl This is. the title" And we run "jrnl -1" Then the output should contain "| the title" Examples: configs - | config_file | - | basic_onefile | - | basic_encrypted | - | basic_folder | - | basic_dayone | + | config_file | + | basic_onefile.yaml | + | basic_encrypted.yaml | + | basic_folder.yaml | + | basic_dayone.yaml | Scenario Outline: CJK entry should be split at fullwidth period without following space. - Given we use the config ".yaml" + Given we use the config "" And we use the password "test" if prompted When we run "jrnl 七転び。八起き" And we run "jrnl -1" Then the output should contain "| 八起き" Examples: configs - | config_file | - | basic_onefile | - | basic_encrypted | - | basic_folder | - | basic_dayone | + | config_file | + | basic_onefile.yaml | + | basic_encrypted.yaml | + | basic_folder.yaml | + | basic_dayone.yaml | Scenario Outline: Writing an entry from command line should store the entry - Given we use the config ".yaml" + Given we use the config "" And we use the password "bad doggie no biscuit" if prompted When we run "jrnl 23 july 2013: A cold and stormy day. I ate crisps on the sofa." Then we should see the message "Entry added" @@ -51,49 +51,47 @@ Feature: Writing new entries. Then the output should contain "2013-07-23 09:00 A cold and stormy day." Examples: configs - | config_file | - | simple | - | empty_folder | - | dayone | - | encrypted | + | config_file | + | simple.yaml | + | empty_folder.yaml | + | dayone.yaml | + | encrypted.yaml | Scenario Outline: Writing a partial entry from command line with edit flag should go to the editor - Given we use the config ".yaml" + Given we use the config "" And we use the password "test" if prompted When we run "jrnl this is a partial --edit" Then we should see the message "Entry added" Then the editor should have been called And the editor file content should be - """ - this is a partial - """ - When we run "jrnl -n 1" - Then the output should contain "this is a partial" + this is a partial Examples: configs - | config_file | - | basic_onefile | - | basic_encrypted | - | basic_dayone | - | basic_folder | + | config_file | + | basic_onefile.yaml | + | basic_encrypted.yaml | + | basic_dayone.yaml | + | basic_folder.yaml | Scenario Outline: Writing an empty entry from the editor should yield "Nothing saved to file" message - Given we use the config ".yaml" + Given we use the config "" + And we write nothing to the editor if opened And we use the password "test" if prompted - When we open the editor and enter nothing + When we run "jrnl --edit" Then the error output should contain "[Nothing saved to file]" + And the editor should have been called Examples: configs - | config_file | - | editor | - | editor_empty_folder | - | dayone | - | basic_encrypted | - | basic_onefile | + | config_file | + | editor.yaml | + | editor_empty_folder.yaml | + | dayone.yaml | + | basic_encrypted.yaml | + | basic_onefile.yaml | @skip Scenario Outline: Writing an empty entry from the command line with no editor should yield nothing - Given we use the config ".yaml" + Given we use the config "" And we use the password "bad doggie no biscuit" if prompted When we run "jrnl" and enter nothing Then the output should be empty @@ -101,15 +99,15 @@ Feature: Writing new entries. And the editor should not have been called Examples: configs - | config_file | - | simple | - | empty_folder | - | encrypted | - # | dayone | @todo + | config_file | + | config_simple.yaml | + | empty_folder.yaml | + | encrypted.yaml | + # | dayone | @todo Scenario Outline: Writing an entry does not print the entire journal # https://github.com/jrnl-org/jrnl/issues/87 - Given we use the config ".yaml" + Given we use the config "" And we use the password "bad doggie no biscuit" if prompted When we run "jrnl 23 july 2013: A cold and stormy day. I ate crisps on the sofa." Then we should see the message "Entry added" @@ -117,33 +115,31 @@ Feature: Writing new entries. Then the output should not contain "Life is good" Examples: configs - | config_file | - | editor | - | editor_empty_folder | - | dayone | - | encrypted | + | config_file | + | editor.yaml | + | editor_empty_folder.yaml | + | dayone.yaml | + | encrypted.yaml | Scenario Outline: Embedded period stays in title - Given we use the config ".yaml" + Given we use the config "" And we use the password "bad doggie no biscuit" if prompted When we run "jrnl 04-24-2014: Created a new website - empty.com. Hope to get a lot of traffic." Then we should see the message "Entry added" When we run "jrnl -1" Then the output should be - """ - 2014-04-24 09:00 Created a new website - empty.com. - | Hope to get a lot of traffic. - """ + 2014-04-24 09:00 Created a new website - empty.com. + | Hope to get a lot of traffic. Examples: configs - | config_file | - | simple | - | empty_folder | - | dayone | - | encrypted | + | config_file | + | simple.yaml | + | empty_folder.yaml | + | dayone.yaml | + | encrypted.yaml | Scenario Outline: Write and read emoji support - Given we use the config ".yaml" + Given we use the config "" And we use the password "bad doggie no biscuit" if prompted When we run "jrnl 23 july 2013: 🌞 sunny day. Saw an 🐘" Then we should see the message "Entry added" @@ -152,14 +148,14 @@ Feature: Writing new entries. And the output should contain "🐘" Examples: configs - | config_file | - | simple | - | empty_folder | - | dayone | - | encrypted | + | config_file | + | simple.yaml | + | empty_folder.yaml | + | dayone.yaml | + | encrypted.yaml | Scenario Outline: Writing an entry at the prompt (no editor) should store the entry - Given we use the config ".yaml" + Given we use the config "" And we use the password "bad doggie no biscuit" if prompted When we run "jrnl" and enter "25 jul 2013: I saw Elvis. He's alive." Then we should get no error @@ -168,10 +164,10 @@ Feature: Writing new entries. And the output should contain "| He's alive." Examples: configs - | config_file | - | simple | - | empty_folder | - | encrypted | + | config_file | + | simple.yaml | + | empty_folder.yaml | + | encrypted.yaml | @todo Scenario: Writing an entry at the prompt (no editor) in DayOne journal @@ -187,26 +183,29 @@ Feature: Writing new entries. Given we use the config "dayone.yaml" When we run "jrnl 01 may 1979: Being born hurts." And we run "jrnl --export json" - Then "entries" in the json output should have 5 elements - And the json output should contain entries.0.creator.software_agent - And the json output should contain entries.0.creator.os_agent - And the json output should contain entries.0.creator.host_name - And the json output should contain entries.0.creator.generation_date - And the json output should contain entries.0.creator.device_agent - And "entries.0.creator.software_agent" in the json output should contain "jrnl" + Then we should get no error + And the output should be valid JSON + Given we parse the output as JSON + Then "entries" in the parsed output should have 5 elements + And "entries.0.creator" in the parsed output should be + software_agent + os_agent + host_name + generation_date + device_agent + And "entries.0.creator.software_agent" in the parsed output should contain + jrnl # fails when system time is UTC (as on Travis-CI) - @skip + # @skip Scenario: Title with an embedded period on DayOne journal Given we use the config "dayone.yaml" When we run "jrnl 04-24-2014: "Ran 6.2 miles today in 1:02:03. I'm feeling sore because I forgot to stretch."" Then we should see the message "Entry added" When we run "jrnl -1" Then the output should be - """ - 2014-04-24 09:00 Ran 6.2 miles today in 1:02:03. - | I'm feeling sore because I forgot to stretch. - """ + 2014-04-24 09:00 Ran 6.2 miles today in 1:02:03. + | I'm feeling sore because I forgot to stretch. Scenario: Opening an folder that's not a DayOne folder should treat as folder journal Given we use the config "empty_folder.yaml" diff --git a/tests/step_defs/conftest.py b/tests/step_defs/conftest.py index 7e10883e..4c1d42d4 100644 --- a/tests/step_defs/conftest.py +++ b/tests/step_defs/conftest.py @@ -8,6 +8,7 @@ from collections import defaultdict from keyring import backend from keyring import set_keyring from keyring import errors +from pathlib import Path import random import string import re @@ -191,12 +192,88 @@ def which_output_stream(): return None +@fixture +def editor_input(): + return None + + +@fixture +def num_args(): + return None + + @fixture def parsed_output(): return {"lang": None, "obj": None} +@fixture +def editor_state(): + return { + "command": "", + "intent": {"method": "r", "input": None}, + "tmpfile": {"name": None, "content": None}, + } + + +@fixture +def editor(editor_state): + def _mock_editor(editor_command): + tmpfile = editor_command[-1] + + editor_state["command"] = editor_command + editor_state["tmpfile"]["name"] = tmpfile + + Path(tmpfile).touch() + with open(tmpfile, editor_state["intent"]["method"]) as f: + # Touch the file so jrnl knows it was edited + if editor_state["intent"]["input"] != None: + f.write(editor_state["intent"]["input"]) + + file_content = f.read() + editor_state["tmpfile"]["content"] = file_content + + return _mock_editor + + # ----- STEPS ----- # +@given(parse("we {editor_method} to the editor if opened\n{editor_input}")) +@given(parse("we {editor_method} nothing to the editor if opened")) +def we_enter_editor(editor_method, editor_input, editor_state): + file_method = editor_state["intent"]["method"] + if editor_method == "write": + file_method = "w+" + elif editor_method == "append": + file_method = "a+" + else: + assert False, f"Method '{editor_method}' not supported" + + editor_state["intent"] = {"method": file_method, "input": editor_input} + + +@then(parse("the editor should have been called")) +@then(parse("the editor should have been called with {num_args} arguments")) +def count_editor_args(num_args, cli_run, editor_state): + assert cli_run["mocks"]["editor"].called + + if isinstance(num_args, int): + assert len(editor_state["command"]) == int(num_args) + + +@then(parse('the editor file content should {comparison} "{str_value}"')) +@then(parse("the editor file content should {comparison} empty")) +@then(parse("the editor file content should {comparison}\n{str_value}")) +def contains_editor_file(comparison, str_value, editor_state): + content = editor_state["tmpfile"]["content"] + # content = f'\n"""\n{content}\n"""\n' + if comparison == "be": + assert content == str_value + elif comparison == "contain": + assert str_value in content + else: + assert False, f"Comparison '{comparison}' not supported" + + @given("we have a keyring", target_fixture="keyring") @given(parse("we have a {keyring_type} keyring"), target_fixture="keyring") def we_have_type_of_keyring(keyring_type): @@ -245,7 +322,15 @@ def use_password_forever(pw): @when('we run "jrnl "') @when('we run "jrnl"') def we_run( - command, config_path, user_input, cli_run, capsys, password, keyring, cache_dir + command, + config_path, + user_input, + cli_run, + capsys, + password, + keyring, + cache_dir, + editor, ): if cache_dir["exists"]: command = command.format(cache_dir=cache_dir["path"]) @@ -270,7 +355,8 @@ def we_run( patch("builtins.input", side_effect=user_input) as mock_input, \ patch("getpass.getpass", side_effect=password) as mock_getpass, \ patch("jrnl.install.get_config_path", return_value=config_path), \ - patch("jrnl.config.get_config_path", return_value=config_path) \ + patch("jrnl.config.get_config_path", return_value=config_path), \ + patch("subprocess.call", side_effect=editor) as mock_editor \ : # @TODO: single point of truth for get_config_path (move from all calls from install to config) try: cli(args) @@ -290,6 +376,7 @@ def we_run( "stdin": mock_stdin, "input": mock_input, "getpass": mock_getpass, + "editor": mock_editor, } @@ -526,11 +613,15 @@ def assert_parsed_output_item_count(node_name, number, parsed_output): assert False, f"Language name {lang} not recognized" -@then(parse('"{field_name}" in the parsed output should be\n{expected_keys}')) -def assert_output_field_content(field_name, expected_keys, cli_run, parsed_output): +@then(parse('"{field_name}" in the parsed output should {comparison}\n{expected_keys}')) +def assert_output_field_content( + field_name, comparison, expected_keys, cli_run, parsed_output +): lang = parsed_output["lang"] obj = parsed_output["obj"] expected_keys = expected_keys.split("\n") + if len(expected_keys) == 1: + expected_keys = expected_keys[0] if lang == "XML": xml_node_names = (node.tag for node in obj) @@ -555,10 +646,22 @@ def assert_output_field_content(field_name, expected_keys, cli_run, parsed_outpu assert node in my_obj, [my_obj.keys(), node] my_obj = my_obj[node] - if type(my_obj) is str: - my_obj = [my_obj] - - assert set(expected_keys) == set(my_obj), [set(my_obj), set(expected_keys)] + if comparison == "be": + if type(my_obj) is str: + assert expected_keys == my_obj, [my_obj, expected_keys] + else: + assert set(expected_keys) == set(my_obj), [ + set(my_obj), + set(expected_keys), + ] + elif comparison == "contain": + if type(my_obj) is str: + assert expected_keys in my_obj, [my_obj, expected_keys] + else: + assert all(elem in my_obj for elem in expected_keys), [ + my_obj, + expected_keys, + ] else: assert False, f"Language name {lang} not recognized" diff --git a/tests/step_defs/test_features.py b/tests/step_defs/test_features.py index 145f8ed6..4c8d5ef9 100644 --- a/tests/step_defs/test_features.py +++ b/tests/step_defs/test_features.py @@ -5,13 +5,13 @@ scenarios("../features/core.feature") scenarios("../features/datetime.feature") scenarios("../features/delete.feature") scenarios("../features/encrypt.feature") -# scenarios("../features/file_storage.feature") +scenarios("../features/file_storage.feature") scenarios("../features/format.feature") -# scenarios("../features/import.feature") -# scenarios("../features/multiple_journals.feature") +scenarios("../features/import.feature") +scenarios("../features/multiple_journals.feature") scenarios("../features/password.feature") -# scenarios("../features/search.feature") -# scenarios("../features/star.feature") -# scenarios("../features/tag.feature") -# scenarios("../features/upgrade.feature") -# scenarios("../features/write.feature") +scenarios("../features/search.feature") +scenarios("../features/star.feature") +scenarios("../features/tag.feature") +scenarios("../features/upgrade.feature") +scenarios("../features/write.feature")