mirror of
https://github.com/GothenburgBitFactory/timewarrior.git
synced 2025-06-26 10:54:28 +02:00
#21 Add implicit @1 to command 'untag'
- Extend interval assertions - Add interval assertions to tests for command 'tag' - Add interval assertions to tests for command 'untag'
This commit is contained in:
parent
c595132a9c
commit
10dfa64cfa
5 changed files with 184 additions and 64 deletions
|
@ -103,7 +103,6 @@ int CmdTag (
|
|||
//TODO validate (cli, rules, database, i);
|
||||
database.modifyInterval (tracked[tracked.size () - id], i);
|
||||
|
||||
// Feedback.
|
||||
if (rules.getBoolean ("verbose"))
|
||||
{
|
||||
std::cout << "Added " << joinQuotedIfNeeded (" ", tags) << " to @" << id << '\n';
|
||||
|
|
|
@ -39,15 +39,11 @@ int CmdUntag (
|
|||
{
|
||||
// Gather IDs and TAGs.
|
||||
std::set <int> ids = cli.getIds ();
|
||||
std::vector<std::string> tags = cli.getTags ();
|
||||
|
||||
if (ids.empty ())
|
||||
throw std::string ("IDs must be specified. See 'timew help untag'.");
|
||||
|
||||
std::vector <std::string> tags;
|
||||
for (auto& arg : cli._args)
|
||||
if (tags.empty ())
|
||||
{
|
||||
if (arg.hasTag ("TAG"))
|
||||
tags.push_back (arg.attribute ("raw"));
|
||||
throw std::string ("At least one tag must be specified. See 'timew help untag'.");
|
||||
}
|
||||
|
||||
// Load the data.
|
||||
|
@ -78,7 +74,22 @@ int CmdUntag (
|
|||
}
|
||||
}
|
||||
|
||||
// Apply tags to ids.
|
||||
if (ids.empty ())
|
||||
{
|
||||
if (tracked.empty ())
|
||||
{
|
||||
throw std::string ("There is no active time tracking.");
|
||||
}
|
||||
|
||||
if (!tracked.back ().range.is_open ())
|
||||
{
|
||||
throw std::string ("At least one ID must be specified. See 'timew help tag'.");
|
||||
}
|
||||
|
||||
ids.insert (1);
|
||||
}
|
||||
|
||||
// Remove tags from ids.
|
||||
for (auto& id : ids)
|
||||
{
|
||||
if (id > static_cast <int> (tracked.size ()))
|
||||
|
|
|
@ -13,19 +13,62 @@ class BaseTestCase(unittest.TestCase):
|
|||
|
||||
|
||||
class TestCase(BaseTestCase):
|
||||
def assertInterval(self, interval, expectedStart=None, expectedEnd=None, expectedTags=None, description=None):
|
||||
self.assertTrue('start' in interval)
|
||||
self.assertTrue('end' in interval)
|
||||
def assertOpenInterval(self, interval,
|
||||
expectedStart=None,
|
||||
expectedTags=None,
|
||||
description="interval"):
|
||||
self.assertTrue("start" in interval, "{} does not contain a start date".format(description))
|
||||
self.assertFalse("end" in interval, "{} does contain an end date".format(description))
|
||||
|
||||
return self.assertInterval(interval,
|
||||
expectedStart=expectedStart,
|
||||
expectedEnd=None,
|
||||
expectedTags=expectedTags,
|
||||
description=description)
|
||||
|
||||
def assertClosedInterval(self, interval,
|
||||
expectedStart=None,
|
||||
expectedEnd=None,
|
||||
expectedTags=None,
|
||||
description="interval"):
|
||||
self.assertTrue("start" in interval, "{} does not contain a start date".format(description))
|
||||
self.assertTrue("end" in interval, "{} does not contain an end date".format(description))
|
||||
|
||||
return self.assertInterval(interval,
|
||||
expectedStart=expectedStart,
|
||||
expectedEnd=expectedEnd,
|
||||
expectedTags=expectedTags,
|
||||
description=description)
|
||||
|
||||
def assertInterval(self, interval,
|
||||
expectedStart=None,
|
||||
expectedEnd=None,
|
||||
expectedTags=None,
|
||||
description="interval"):
|
||||
if expectedStart:
|
||||
self.assertEqual(interval['start'], expectedStart, description)
|
||||
self.assertEqual(
|
||||
interval["start"],
|
||||
expectedStart,
|
||||
"start time of {} does not match (expected: {}, actual: {})".format(description,
|
||||
expectedStart,
|
||||
interval["start"]))
|
||||
|
||||
if expectedEnd:
|
||||
self.assertEqual(interval['end'], expectedEnd, description)
|
||||
self.assertEqual(
|
||||
interval["end"],
|
||||
expectedEnd,
|
||||
"end time of {} does not match (expected: {}, actual: {})".format(description,
|
||||
expectedEnd,
|
||||
interval["end"]))
|
||||
|
||||
if expectedTags:
|
||||
self.assertTrue('tags' in interval)
|
||||
self.assertEqual(interval['tags'], expectedTags, description)
|
||||
self.assertTrue("tags" in interval)
|
||||
self.assertEqual(
|
||||
interval["tags"],
|
||||
expectedTags,
|
||||
"tags of {} do not match (expected: {}, actual: {})". format(description,
|
||||
expectedTags,
|
||||
interval["tags"]))
|
||||
|
||||
|
||||
# vim: ai sts=4 et sw=4
|
||||
|
|
|
@ -116,12 +116,11 @@ class TestDelete(TestCase):
|
|||
j = self.t.export()
|
||||
print(j)
|
||||
self.assertEqual(len(j), 1)
|
||||
self.assertInterval(
|
||||
j[0],
|
||||
'{:%Y%m%dT%H}0000Z'.format(now_utc-timedelta(hours=5)),
|
||||
'{:%Y%m%dT%H}0000Z'.format(now_utc-timedelta(hours=4)),
|
||||
['foo'],
|
||||
'remaining interval')
|
||||
self.assertClosedInterval(j[0],
|
||||
expectedStart='{:%Y%m%dT%H}0000Z'.format(now_utc-timedelta(hours=5)),
|
||||
expectedEnd='{:%Y%m%dT%H}0000Z'.format(now_utc-timedelta(hours=4)),
|
||||
expectedTags=['foo'],
|
||||
description='remaining interval')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
152
test/tag.t
152
test/tag.t
|
@ -47,28 +47,35 @@ class TestTag(TestCase):
|
|||
"""Add a tag to an open interval"""
|
||||
self.t("start 30min ago")
|
||||
code, out, err = self.t("tag @1 foo")
|
||||
self.assertIn('Added foo to @1', out)
|
||||
self.assertIn("Added foo to @1", out)
|
||||
|
||||
j = self.t.export()
|
||||
self.assertOpenInterval(j[0], expectedTags=["foo"])
|
||||
|
||||
def test_should_use_default_on_missing_id_and_active_time_tracking(self):
|
||||
"""Use open interval on missing id and active time tracking"""
|
||||
"""Use open interval when adding tags with missing id and active time tracking"""
|
||||
self.t("track yesterday for 1hour foo")
|
||||
self.t("start 30min ago bar")
|
||||
code, out, err = self.t("tag baz")
|
||||
self.assertIn("Added baz to @1", out)
|
||||
|
||||
j = self.t.export()
|
||||
self.assertClosedInterval(j[0], expectedTags=["foo"])
|
||||
self.assertOpenInterval(j[1], expectedTags=["bar", "baz"])
|
||||
|
||||
def test_should_fail_on_missing_id_and_empty_database(self):
|
||||
"""Missing id on empty database is an error"""
|
||||
"""Adding tag with missing id on empty database is an error"""
|
||||
code, out, err = self.t.runError("tag foo")
|
||||
self.assertIn("There is no active time tracking.", err)
|
||||
|
||||
def test_should_fail_on_missing_id_and_inactive_time_tracking(self):
|
||||
"""Missing id on inactive time tracking is an error"""
|
||||
"""Adding tag with missing id on inactive time tracking is an error"""
|
||||
self.t("track yesterday for 1hour")
|
||||
code, out, err = self.t.runError("tag foo")
|
||||
self.assertIn("At least one ID must be specified.", err)
|
||||
|
||||
def test_should_fail_on_no_tags(self):
|
||||
"""No tags is an error"""
|
||||
"""Calling command 'tag' without tags is an error"""
|
||||
self.t("track yesterday for 1hour")
|
||||
code, out, err = self.t.runError("tag @1")
|
||||
self.assertIn("At least one tag must be specified.", err)
|
||||
|
@ -77,34 +84,51 @@ class TestTag(TestCase):
|
|||
"""Add a tag to an closed interval"""
|
||||
self.t("track yesterday for 1hour")
|
||||
code, out, err = self.t("tag @1 foo")
|
||||
self.assertIn('Added foo to @1', out)
|
||||
self.assertIn("Added foo to @1", out)
|
||||
|
||||
j = self.t.export()
|
||||
self.assertClosedInterval(j[0], expectedTags=["foo"])
|
||||
|
||||
def test_add_tags_to_open_interval(self):
|
||||
"""Add tags to an open interval"""
|
||||
self.t("start 30min ago")
|
||||
code, out, err = self.t("tag @1 foo bar")
|
||||
self.assertIn('Added foo bar to @1', out)
|
||||
self.assertIn("Added foo bar to @1", out)
|
||||
|
||||
j = self.t.export()
|
||||
self.assertOpenInterval(j[0], expectedTags=["bar", "foo"])
|
||||
|
||||
def test_add_tags_to_closed_interval(self):
|
||||
"""Add tags to an closed interval"""
|
||||
self.t("track yesterday for 1hour")
|
||||
code, out, err = self.t("tag @1 foo bar")
|
||||
self.assertIn('Added foo bar to @1', out)
|
||||
self.assertIn("Added foo bar to @1", out)
|
||||
|
||||
j = self.t.export()
|
||||
self.assertClosedInterval(j[0], expectedTags=["bar", "foo"])
|
||||
|
||||
def test_add_tag_to_multiple_intervals(self):
|
||||
"""Add a tag to multiple intervals"""
|
||||
self.t("track 2016-01-01T00:00:00 - 2016-01-01T01:00:00")
|
||||
self.t("track 2016-01-01T01:00:00 - 2016-01-01T02:00:00")
|
||||
self.t("track 2016-01-01T00:00:00 - 2016-01-01T01:00:00 one")
|
||||
self.t("track 2016-01-01T01:00:00 - 2016-01-01T02:00:00 two")
|
||||
code, out, err = self.t("tag @1 @2 foo")
|
||||
self.assertIn('Added foo to @1\nAdded foo to @2', out)
|
||||
self.assertIn("Added foo to @1\nAdded foo to @2", out)
|
||||
|
||||
j = self.t.export()
|
||||
self.assertClosedInterval(j[0], expectedTags=["foo", "one"])
|
||||
self.assertClosedInterval(j[1], expectedTags=["foo", "two"])
|
||||
|
||||
def test_add_tags_to_multiple_intervals(self):
|
||||
"""Add tags to multiple intervals"""
|
||||
self.t("track 2016-01-01T00:00:00 - 2016-01-01T01:00:00")
|
||||
self.t("track 2016-01-01T01:00:00 - 2016-01-01T02:00:00")
|
||||
self.t("track 2016-01-01T00:00:00 - 2016-01-01T01:00:00 one")
|
||||
self.t("track 2016-01-01T01:00:00 - 2016-01-01T02:00:00 two")
|
||||
code, out, err = self.t("tag @1 @2 foo bar")
|
||||
self.assertIn('Added foo bar to @1\nAdded foo bar to @2', out)
|
||||
|
||||
j = self.t.export()
|
||||
self.assertClosedInterval(j[0], expectedTags=["bar", "foo", "one"])
|
||||
self.assertClosedInterval(j[1], expectedTags=["bar", "foo", "two"])
|
||||
|
||||
def test_tag_synthetic_interval(self):
|
||||
"""Tag a synthetic interval."""
|
||||
now = datetime.now()
|
||||
|
@ -134,17 +158,15 @@ class TestTag(TestCase):
|
|||
j = self.t.export()
|
||||
|
||||
self.assertEqual(len(j), 2)
|
||||
self.assertTrue('start' in j[0])
|
||||
self.assertEqual(j[0]['start'], '{:%Y%m%dT%H}4500Z'.format(now_utc-timedelta(hours=5)), 'start time of modified interval does not match')
|
||||
self.assertTrue('end' in j[0])
|
||||
self.assertEqual(j[0]['end'], '{:%Y%m%dT%H}0000Z'.format(now_utc - timedelta(hours=4)), 'end time of modified interval does not match')
|
||||
self.assertTrue('tags' in j[0])
|
||||
self.assertEqual(j[0]['tags'], ['bar', 'foo'], 'tags of modified interval do not match')
|
||||
self.assertTrue('start' in j[1])
|
||||
self.assertEqual(j[1]['start'], '{:%Y%m%dT%H}0000Z'.format(now_utc - timedelta(hours=3)), 'start time of unmodified interval does not match')
|
||||
self.assertFalse('end' in j[1])
|
||||
self.assertTrue('tags' in j[1])
|
||||
self.assertEqual(j[1]['tags'], ['foo'], 'tags of unmodified interval do not match')
|
||||
self.assertClosedInterval(j[0],
|
||||
expectedStart="{:%Y%m%dT%H}4500Z".format(now_utc - timedelta(hours=5)),
|
||||
expectedEnd="{:%Y%m%dT%H}0000Z".format(now_utc - timedelta(hours=4)),
|
||||
expectedTags=["bar", "foo"],
|
||||
description="modified interval")
|
||||
self.assertOpenInterval(j[1],
|
||||
expectedStart="{:%Y%m%dT%H}0000Z".format(now_utc - timedelta(hours=3)),
|
||||
expectedTags=["foo"],
|
||||
description="unmodified interval")
|
||||
|
||||
def test_tag_with_identical_ids(self):
|
||||
self.t("track 2016-01-01T00:00:00 - 2016-01-01T01:00:00")
|
||||
|
@ -153,7 +175,7 @@ class TestTag(TestCase):
|
|||
j = self.t.export()
|
||||
|
||||
self.assertEquals(len(j), 1)
|
||||
self.assertEqual(j[0]['tags'], ['foo'])
|
||||
self.assertClosedInterval(j[0], expectedTags=["foo"])
|
||||
|
||||
|
||||
class TestUntag(TestCase):
|
||||
|
@ -167,38 +189,86 @@ class TestUntag(TestCase):
|
|||
code, out, err = self.t("untag @1 foo")
|
||||
self.assertIn('Removed foo from @1', out)
|
||||
|
||||
j = self.t.export()
|
||||
self.assertOpenInterval(j[0], expectedTags=["bar", "baz"])
|
||||
|
||||
def test_should_use_default_on_missing_id_and_active_time_tracking(self):
|
||||
"""Use open interval when removing tags with missing id and active time tracking"""
|
||||
self.t("track yesterday for 1hour foo")
|
||||
self.t("start 30min ago bar baz")
|
||||
code, out, err = self.t("untag baz")
|
||||
self.assertIn("Removed baz from @1", out)
|
||||
|
||||
j = self.t.export()
|
||||
self.assertClosedInterval(j[0], expectedTags=["foo"])
|
||||
self.assertOpenInterval(j[1], expectedTags=["bar"])
|
||||
|
||||
def test_should_fail_on_missing_id_and_empty_database(self):
|
||||
"""Removing tag with missing id on empty database is an error"""
|
||||
code, out, err = self.t.runError("untag foo")
|
||||
self.assertIn("There is no active time tracking.", err)
|
||||
|
||||
def test_should_fail_on_missing_id_and_inactive_time_tracking(self):
|
||||
"""Removing tag with missing id on inactive time tracking is an error"""
|
||||
self.t("track yesterday for 1hour")
|
||||
code, out, err = self.t.runError("untag foo")
|
||||
self.assertIn("At least one ID must be specified.", err)
|
||||
|
||||
def test_should_fail_on_no_tags(self):
|
||||
"""Calling command 'untag' without tags is an error"""
|
||||
self.t("track yesterday for 1hour")
|
||||
code, out, err = self.t.runError("untag @1")
|
||||
self.assertIn("At least one tag must be specified.", err)
|
||||
|
||||
def test_remove_tag_from_closed_interval(self):
|
||||
"""Remove a tag from an closed interval"""
|
||||
"""Remove a tag from a closed interval"""
|
||||
self.t("track yesterday for 1hour foo bar baz")
|
||||
code, out, err = self.t("untag @1 foo")
|
||||
self.assertIn('Removed foo from @1', out)
|
||||
|
||||
j = self.t.export()
|
||||
self.assertClosedInterval(j[0], expectedTags=["bar", "baz"])
|
||||
|
||||
def test_remove_tags_from_open_interval(self):
|
||||
"""Remove tags from an open interval"""
|
||||
self.t("start 30min ago foo bar baz")
|
||||
code, out, err = self.t("untag @1 foo bar")
|
||||
self.assertIn('Removed foo bar from @1', out)
|
||||
|
||||
j = self.t.export()
|
||||
self.assertOpenInterval(j[0], expectedTags=["baz"])
|
||||
|
||||
def test_remove_tags_from_closed_interval(self):
|
||||
"""Remove tags from an closed interval"""
|
||||
self.t("track yesterday for 1hour foo bar baz")
|
||||
code, out, err = self.t("untag @1 foo bar")
|
||||
self.assertIn('Removed foo bar from @1', out)
|
||||
|
||||
j = self.t.export()
|
||||
self.assertClosedInterval(j[0], expectedTags=["baz"])
|
||||
|
||||
def test_remove_tag_from_multiple_intervals(self):
|
||||
"""Remove a tag from multiple intervals"""
|
||||
self.t("track 2016-01-01T00:00:00 - 2016-01-01T01:00:00 foo bar baz")
|
||||
self.t("track 2016-01-01T01:00:00 - 2016-01-01T02:00:00 foo bar baz")
|
||||
self.t("track 2016-01-01T00:00:00 - 2016-01-01T01:00:00 foo bar one")
|
||||
self.t("track 2016-01-01T01:00:00 - 2016-01-01T02:00:00 foo bar two")
|
||||
code, out, err = self.t("untag @1 @2 foo")
|
||||
self.assertIn('Removed foo from @1\nRemoved foo from @2', out)
|
||||
|
||||
j = self.t.export()
|
||||
self.assertClosedInterval(j[0], expectedTags=["bar", "one"])
|
||||
self.assertClosedInterval(j[1], expectedTags=["bar", "two"])
|
||||
|
||||
def test_remove_tags_from_multiple_intervals(self):
|
||||
"""Remove tags from multiple intervals"""
|
||||
self.t("track 2016-01-01T00:00:00 - 2016-01-01T01:00:00 foo bar baz")
|
||||
self.t("track 2016-01-01T01:00:00 - 2016-01-01T02:00:00 foo bar baz")
|
||||
self.t("track 2016-01-01T00:00:00 - 2016-01-01T01:00:00 foo bar one")
|
||||
self.t("track 2016-01-01T01:00:00 - 2016-01-01T02:00:00 foo bar two")
|
||||
code, out, err = self.t("untag @1 @2 foo bar")
|
||||
self.assertIn('Removed foo bar from @1\nRemoved foo bar from @2', out)
|
||||
|
||||
j = self.t.export()
|
||||
self.assertClosedInterval(j[0], expectedTags=["one"])
|
||||
self.assertClosedInterval(j[1], expectedTags=["two"])
|
||||
|
||||
def test_untag_synthetic_interval(self):
|
||||
"""Untag a synthetic interval."""
|
||||
now = datetime.now()
|
||||
|
@ -228,17 +298,15 @@ class TestUntag(TestCase):
|
|||
j = self.t.export()
|
||||
|
||||
self.assertEqual(len(j), 2)
|
||||
self.assertTrue('start' in j[0])
|
||||
self.assertEqual(j[0]['start'], '{:%Y%m%dT%H}4500Z'.format(now_utc-timedelta(hours=5)), 'start time of modified interval does not match')
|
||||
self.assertTrue('end' in j[0])
|
||||
self.assertEqual(j[0]['end'], '{:%Y%m%dT%H}0000Z'.format(now_utc - timedelta(hours=4)), 'end time of modified interval does not match')
|
||||
self.assertTrue('tags' in j[0])
|
||||
self.assertEqual(j[0]['tags'], ['bar'], 'tags of modified interval do not match')
|
||||
self.assertTrue('start' in j[1])
|
||||
self.assertEqual(j[1]['start'], '{:%Y%m%dT%H}0000Z'.format(now_utc - timedelta(hours=3)), 'start time of unmodified interval does not match')
|
||||
self.assertFalse('end' in j[1])
|
||||
self.assertTrue('tags' in j[1])
|
||||
self.assertEqual(j[1]['tags'], ['bar', 'foo'], 'tags of unmodified interval do not match')
|
||||
self.assertClosedInterval(j[0],
|
||||
expectedStart="{:%Y%m%dT%H}4500Z".format(now_utc - timedelta(hours=5)),
|
||||
expectedEnd="{:%Y%m%dT%H}0000Z".format(now_utc - timedelta(hours=4)),
|
||||
expectedTags=["bar"],
|
||||
description="modified interval")
|
||||
self.assertOpenInterval(j[1],
|
||||
expectedStart="{:%Y%m%dT%H}0000Z".format(now_utc - timedelta(hours=3)),
|
||||
expectedTags=["bar", "foo"],
|
||||
description="unmodified interval")
|
||||
|
||||
def test_untag_with_identical_ids(self):
|
||||
self.t("track 2016-01-01T00:00:00 - 2016-01-01T01:00:00 foo bar")
|
||||
|
@ -247,7 +315,7 @@ class TestUntag(TestCase):
|
|||
j = self.t.export()
|
||||
|
||||
self.assertEquals(len(j), 1)
|
||||
self.assertEqual(j[0]['tags'], ['bar'])
|
||||
self.assertClosedInterval(j[0], expectedTags=["bar"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue