From 1ef85c5b9b2766b1e609216278c8947a11649a1f Mon Sep 17 00:00:00 2001 From: Marten Gajda <marten@dmfs.org> Date: Wed, 13 Dec 2017 22:40:55 +0100 Subject: [PATCH] Make sure non-recurring tasks get an INSTANCE_ORIGINAL_TIME of 0. Implements #577 This resulted in a minor SRP-ish refactoring to separate steps of instance value creation. --- opentasks-provider/build.gradle | 2 + .../dmfs/provider/tasks/TaskProviderTest.java | 14 +- .../tasks/processors/tasks/Instantiating.java | 18 +- .../processors/tasks/instancedata/Dated.java | 51 +++++ .../tasks/instancedata/DueDated.java} | 18 +- .../tasks/instancedata/Enduring.java | 57 ++++++ .../tasks/instancedata/Overridden.java | 62 ++++++ .../tasks/instancedata/StartDated.java | 39 ++++ .../tasks/instancedata/TaskRelated.java} | 6 +- .../instancedata/VanillaInstanceData.java | 45 +++++ .../tasks/utils/InstanceDateTimeData.java | 111 ----------- .../tasks/utils/InstanceValuesIterable.java | 37 ++-- .../org/dmfs/provider/tasks/utils/Range.java | 3 + .../org/dmfs/provider/tasks/utils/Zipped.java | 43 ++++ .../tasks/instancedata/DatedTest.java | 62 ++++++ .../tasks/instancedata/DueDatedTest.java | 84 ++++++++ .../tasks/instancedata/EnduringTest.java | 80 ++++++++ .../tasks/instancedata/OverriddenTest.java | 110 +++++++++++ .../tasks/instancedata/StartDatedTest.java | 84 ++++++++ .../tasks/instancedata/TaskRelatedTest.java | 44 +++++ .../instancedata/VanillaInstanceDataTest.java | 52 +++++ .../tasks/utils/ContentValuesWithLong.java | 57 ++++++ .../tasks/utils/InstanceDateTimeDataTest.java | 184 ------------------ .../dmfs/provider/tasks/utils/ZippedTest.java | 56 ++++++ 24 files changed, 973 insertions(+), 346 deletions(-) create mode 100644 opentasks-provider/src/main/java/org/dmfs/provider/tasks/processors/tasks/instancedata/Dated.java rename opentasks-provider/src/main/java/org/dmfs/provider/tasks/{utils/SingleValueFunction.java => processors/tasks/instancedata/DueDated.java} (52%) create mode 100644 opentasks-provider/src/main/java/org/dmfs/provider/tasks/processors/tasks/instancedata/Enduring.java create mode 100644 opentasks-provider/src/main/java/org/dmfs/provider/tasks/processors/tasks/instancedata/Overridden.java create mode 100644 opentasks-provider/src/main/java/org/dmfs/provider/tasks/processors/tasks/instancedata/StartDated.java rename opentasks-provider/src/main/java/org/dmfs/provider/tasks/{utils/WithTaskId.java => processors/tasks/instancedata/TaskRelated.java} (86%) create mode 100644 opentasks-provider/src/main/java/org/dmfs/provider/tasks/processors/tasks/instancedata/VanillaInstanceData.java delete mode 100644 opentasks-provider/src/main/java/org/dmfs/provider/tasks/utils/InstanceDateTimeData.java create mode 100644 opentasks-provider/src/main/java/org/dmfs/provider/tasks/utils/Zipped.java create mode 100644 opentasks-provider/src/test/java/org/dmfs/provider/tasks/processors/tasks/instancedata/DatedTest.java create mode 100644 opentasks-provider/src/test/java/org/dmfs/provider/tasks/processors/tasks/instancedata/DueDatedTest.java create mode 100644 opentasks-provider/src/test/java/org/dmfs/provider/tasks/processors/tasks/instancedata/EnduringTest.java create mode 100644 opentasks-provider/src/test/java/org/dmfs/provider/tasks/processors/tasks/instancedata/OverriddenTest.java create mode 100644 opentasks-provider/src/test/java/org/dmfs/provider/tasks/processors/tasks/instancedata/StartDatedTest.java create mode 100644 opentasks-provider/src/test/java/org/dmfs/provider/tasks/processors/tasks/instancedata/TaskRelatedTest.java create mode 100644 opentasks-provider/src/test/java/org/dmfs/provider/tasks/processors/tasks/instancedata/VanillaInstanceDataTest.java create mode 100644 opentasks-provider/src/test/java/org/dmfs/provider/tasks/utils/ContentValuesWithLong.java delete mode 100644 opentasks-provider/src/test/java/org/dmfs/provider/tasks/utils/InstanceDateTimeDataTest.java create mode 100644 opentasks-provider/src/test/java/org/dmfs/provider/tasks/utils/ZippedTest.java diff --git a/opentasks-provider/build.gradle b/opentasks-provider/build.gradle index 5fa66ba28..c96e40055 100644 --- a/opentasks-provider/build.gradle +++ b/opentasks-provider/build.gradle @@ -43,5 +43,7 @@ dependencies { androidTestImplementation 'com.android.support.test:rules:0.5' testImplementation 'org.robolectric:robolectric:' + ROBOLECTRIC_VERSION testImplementation 'junit:junit:4.12' + testImplementation 'org.mockito:mockito-core:2.10.0' + testImplementation 'org.dmfs:jems-testing:' + JEMS_VERSION testImplementation 'org.hamcrest:hamcrest-all:1.3' } diff --git a/opentasks-provider/src/androidTest/java/org/dmfs/provider/tasks/TaskProviderTest.java b/opentasks-provider/src/androidTest/java/org/dmfs/provider/tasks/TaskProviderTest.java index 3c007d179..c30f40033 100644 --- a/opentasks-provider/src/androidTest/java/org/dmfs/provider/tasks/TaskProviderTest.java +++ b/opentasks-provider/src/androidTest/java/org/dmfs/provider/tasks/TaskProviderTest.java @@ -234,7 +234,7 @@ public void testInsertTaskWithStartAndDue() new Assert<>(task, new TimeData(start, due)), new AssertRelated<>(new InstanceTable(mAuthority), Instances.TASK_ID, task, new AllOf( new EqArg(Instances.INSTANCE_START, start.getTimestamp()), - new EqArg(Instances.INSTANCE_ORIGINAL_TIME, start.getTimestamp()), + new EqArg(Instances.INSTANCE_ORIGINAL_TIME, 0), new EqArg(Instances.INSTANCE_DUE, due.getTimestamp()), new EqArg(Instances.INSTANCE_START_SORTING, start.shiftTimeZone(TimeZone.getDefault()).getInstance()), new EqArg(Instances.INSTANCE_DUE_SORTING, due.shiftTimeZone(TimeZone.getDefault()).getInstance()), @@ -269,7 +269,7 @@ public void testInsertTaskWithStartAndDueMovedForward() new Assert<>(task, new TimeData(startNew, dueNew)), new AssertRelated<>(new InstanceTable(mAuthority), Instances.TASK_ID, task, new AllOf( new EqArg(Instances.INSTANCE_START, startNew.getTimestamp()), - new EqArg(Instances.INSTANCE_ORIGINAL_TIME, startNew.getTimestamp()), + new EqArg(Instances.INSTANCE_ORIGINAL_TIME, 0), new EqArg(Instances.INSTANCE_DUE, dueNew.getTimestamp()), new EqArg(Instances.INSTANCE_START_SORTING, startNew.shiftTimeZone(TimeZone.getDefault()).getInstance()), new EqArg(Instances.INSTANCE_DUE_SORTING, dueNew.shiftTimeZone(TimeZone.getDefault()).getInstance()), @@ -304,7 +304,7 @@ public void testInsertTaskWithStartAndDueMovedBackwards() new Assert<>(task, new TimeData(startNew, dueNew)), new AssertRelated<>(new InstanceTable(mAuthority), Instances.TASK_ID, task, new AllOf( new EqArg(Instances.INSTANCE_START, startNew.getTimestamp()), - new EqArg(Instances.INSTANCE_ORIGINAL_TIME, startNew.getTimestamp()), + new EqArg(Instances.INSTANCE_ORIGINAL_TIME, 0), new EqArg(Instances.INSTANCE_DUE, dueNew.getTimestamp()), new EqArg(Instances.INSTANCE_START_SORTING, startNew.shiftTimeZone(TimeZone.getDefault()).getInstance()), new EqArg(Instances.INSTANCE_DUE_SORTING, dueNew.shiftTimeZone(TimeZone.getDefault()).getInstance()), @@ -335,7 +335,7 @@ public void testInsertTaskWithStartAndDueAddedAfterwards() new Assert<>(task, new TimeData(start, due)), new AssertRelated<>(new InstanceTable(mAuthority), Instances.TASK_ID, task, new AllOf( new EqArg(Instances.INSTANCE_START, start.getTimestamp()), - new EqArg(Instances.INSTANCE_ORIGINAL_TIME, start.getTimestamp()), + new EqArg(Instances.INSTANCE_ORIGINAL_TIME, 0), new EqArg(Instances.INSTANCE_DUE, due.getTimestamp()), new EqArg(Instances.INSTANCE_START_SORTING, start.shiftTimeZone(TimeZone.getDefault()).getInstance()), new EqArg(Instances.INSTANCE_DUE_SORTING, due.shiftTimeZone(TimeZone.getDefault()).getInstance()), @@ -371,7 +371,7 @@ public void testInsertWithStartAndDuration() new EqArg(Instances.INSTANCE_DURATION, durationMillis), new EqArg(Instances.INSTANCE_START_SORTING, start.shiftTimeZone(TimeZone.getDefault()).getInstance()), new EqArg(Instances.INSTANCE_DUE_SORTING, start.addDuration(duration).shiftTimeZone(TimeZone.getDefault()).getInstance()), - new EqArg(Instances.INSTANCE_ORIGINAL_TIME, start.getTimestamp()), + new EqArg(Instances.INSTANCE_ORIGINAL_TIME, 0), new EqArg(Tasks.TZ, "UTC") )) )); @@ -407,7 +407,7 @@ public void testInsertWithStartAndDurationChangeTimeZone() new EqArg(Instances.INSTANCE_DURATION, durationMillis), new EqArg(Instances.INSTANCE_START_SORTING, start.shiftTimeZone(TimeZone.getDefault()).getInstance()), new EqArg(Instances.INSTANCE_DUE_SORTING, start.addDuration(duration).shiftTimeZone(TimeZone.getDefault()).getInstance()), - new EqArg(Instances.INSTANCE_ORIGINAL_TIME, start.getTimestamp()), + new EqArg(Instances.INSTANCE_ORIGINAL_TIME, 0), new EqArg(Tasks.TZ, "America/New_York") )) )); @@ -447,7 +447,7 @@ public void testUpdateDue() throws Exception new EqArg(Instances.INSTANCE_DURATION, due2.getTimestamp() - start.getTimestamp()), new EqArg(Instances.INSTANCE_START_SORTING, start.shiftTimeZone(TimeZone.getDefault()).getInstance()), new EqArg(Instances.INSTANCE_DUE_SORTING, due2.shiftTimeZone(TimeZone.getDefault()).getInstance()), - new EqArg(Instances.INSTANCE_ORIGINAL_TIME, start.getTimestamp()), + new EqArg(Instances.INSTANCE_ORIGINAL_TIME, 0), new EqArg(Tasks.TZ, "UTC") )) )); diff --git a/opentasks-provider/src/main/java/org/dmfs/provider/tasks/processors/tasks/Instantiating.java b/opentasks-provider/src/main/java/org/dmfs/provider/tasks/processors/tasks/Instantiating.java index 19d568bd2..059ada774 100644 --- a/opentasks-provider/src/main/java/org/dmfs/provider/tasks/processors/tasks/Instantiating.java +++ b/opentasks-provider/src/main/java/org/dmfs/provider/tasks/processors/tasks/Instantiating.java @@ -20,7 +20,6 @@ import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; -import org.dmfs.jems.function.BiFunction; import org.dmfs.jems.iterable.composite.Diff; import org.dmfs.jems.iterable.decorators.Mapped; import org.dmfs.jems.pair.Pair; @@ -33,8 +32,7 @@ import org.dmfs.provider.tasks.utils.InstanceValuesIterable; import org.dmfs.provider.tasks.utils.Limited; import org.dmfs.provider.tasks.utils.Range; -import org.dmfs.provider.tasks.utils.SingleValueFunction; -import org.dmfs.provider.tasks.utils.WithTaskId; +import org.dmfs.provider.tasks.processors.tasks.instancedata.TaskRelated; import org.dmfs.tasks.contract.TaskContract; import java.util.Locale; @@ -136,7 +134,7 @@ private void createInstances(SQLiteDatabase db, TaskAdapter taskAdapter, long id // TODO: only limit future instances for (Single<ContentValues> values : new Limited<>(INSTANCE_COUNT_LIMIT, new InstanceValuesIterable(taskAdapter))) { - db.insert(TaskDatabaseHelper.Tables.INSTANCES, "", new WithTaskId(id, values).value()); + db.insert(TaskDatabaseHelper.Tables.INSTANCES, "", new TaskRelated(id, values).value()); } } @@ -181,16 +179,12 @@ private void updateInstances(SQLiteDatabase db, TaskAdapter taskAdapter, long id // TODO: once we actually support recurrence we should only count future instances Iterable<Pair<Optional<ContentValues>, Optional<Integer>>> diff = new Diff<>( - new Mapped<>(new SingleValueFunction<ContentValues>(), new Limited<>(INSTANCE_COUNT_LIMIT, new InstanceValuesIterable(taskAdapter))), + new Mapped<>(Single::value, new Limited<>(INSTANCE_COUNT_LIMIT, new InstanceValuesIterable(taskAdapter))), new Range(existingInstances.getCount()), - new BiFunction<ContentValues, Integer, Integer>() + (newInstanceValues, cursorRow) -> { - @Override - public Integer value(ContentValues newInstanceValues, Integer cursorRow) - { - existingInstances.moveToPosition(cursorRow); - return (int) (existingInstances.getLong(startIdx) - newInstanceValues.getAsLong(TaskContract.Instances.INSTANCE_ORIGINAL_TIME)); - } + existingInstances.moveToPosition(cursorRow); + return (int) (existingInstances.getLong(startIdx) - newInstanceValues.getAsLong(TaskContract.Instances.INSTANCE_ORIGINAL_TIME)); }); // sync the instances table with the new instances diff --git a/opentasks-provider/src/main/java/org/dmfs/provider/tasks/processors/tasks/instancedata/Dated.java b/opentasks-provider/src/main/java/org/dmfs/provider/tasks/processors/tasks/instancedata/Dated.java new file mode 100644 index 000000000..3989e3d80 --- /dev/null +++ b/opentasks-provider/src/main/java/org/dmfs/provider/tasks/processors/tasks/instancedata/Dated.java @@ -0,0 +1,51 @@ +/* + * Copyright 2017 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.provider.tasks.processors.tasks.instancedata; + +import android.content.ContentValues; + +import org.dmfs.jems.single.Single; +import org.dmfs.jems.single.decorators.DelegatingSingle; +import org.dmfs.optional.Optional; +import org.dmfs.provider.tasks.utils.Zipped; +import org.dmfs.rfc5545.DateTime; + +import java.util.TimeZone; + + +/** + * A {@link Single} of date and time {@link ContentValues} of an instance. + * + * @author Marten Gajda + */ +public final class Dated extends DelegatingSingle<ContentValues> +{ + + public Dated(Optional<DateTime> dateTime, String timeStampColumn, String sortingColumn, Single<ContentValues> delegate) + { + super(new Zipped<>( + dateTime, + delegate, + (dateTime1, values) -> + { + // add timestamp and sorting + values.put(timeStampColumn, dateTime1.getTimestamp()); + values.put(sortingColumn, dateTime1.isAllDay() ? dateTime1.getInstance() : dateTime1.shiftTimeZone(TimeZone.getDefault()).getInstance()); + return values; + })); + } +} diff --git a/opentasks-provider/src/main/java/org/dmfs/provider/tasks/utils/SingleValueFunction.java b/opentasks-provider/src/main/java/org/dmfs/provider/tasks/processors/tasks/instancedata/DueDated.java similarity index 52% rename from opentasks-provider/src/main/java/org/dmfs/provider/tasks/utils/SingleValueFunction.java rename to opentasks-provider/src/main/java/org/dmfs/provider/tasks/processors/tasks/instancedata/DueDated.java index 37ec7b9bd..5715d6a01 100644 --- a/opentasks-provider/src/main/java/org/dmfs/provider/tasks/utils/SingleValueFunction.java +++ b/opentasks-provider/src/main/java/org/dmfs/provider/tasks/processors/tasks/instancedata/DueDated.java @@ -14,22 +14,26 @@ * limitations under the License. */ -package org.dmfs.provider.tasks.utils; +package org.dmfs.provider.tasks.processors.tasks.instancedata; + +import android.content.ContentValues; -import org.dmfs.jems.function.Function; import org.dmfs.jems.single.Single; +import org.dmfs.jems.single.decorators.DelegatingSingle; +import org.dmfs.optional.Optional; +import org.dmfs.rfc5545.DateTime; +import org.dmfs.tasks.contract.TaskContract; /** - * A {@link Function} returning the value of a {@link Single}. + * A decorator to a {@link Single} of {@link ContentValues} adding due data. * * @author Marten Gajda */ -public final class SingleValueFunction<T> implements Function<Single<T>, T> +public final class DueDated extends DelegatingSingle<ContentValues> { - @Override - public T value(Single<T> single) + public DueDated(Optional<DateTime> due, Single<ContentValues> delegate) { - return single.value(); + super(new Dated(due, TaskContract.Instances.INSTANCE_DUE, TaskContract.Instances.INSTANCE_DUE_SORTING, delegate)); } } diff --git a/opentasks-provider/src/main/java/org/dmfs/provider/tasks/processors/tasks/instancedata/Enduring.java b/opentasks-provider/src/main/java/org/dmfs/provider/tasks/processors/tasks/instancedata/Enduring.java new file mode 100644 index 000000000..9c1b1b575 --- /dev/null +++ b/opentasks-provider/src/main/java/org/dmfs/provider/tasks/processors/tasks/instancedata/Enduring.java @@ -0,0 +1,57 @@ +/* + * Copyright 2017 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.provider.tasks.processors.tasks.instancedata; + +import android.content.ContentValues; + +import org.dmfs.jems.single.Single; +import org.dmfs.optional.NullSafe; +import org.dmfs.optional.composite.Zipped; +import org.dmfs.tasks.contract.TaskContract; + + +/** + * A decorator for {@link Single}s of Instance {@link ContentValues} which populates the {@link TaskContract.Instances#INSTANCE_DURATION} field based on the + * already populated {@link TaskContract.Instances#INSTANCE_START} and {@link TaskContract.Instances#INSTANCE_DUE} fields. + * + * @author Marten Gajda + */ +public final class Enduring implements Single<ContentValues> +{ + private final Single<ContentValues> mDelegate; + + + public Enduring(Single<ContentValues> delegate) + { + mDelegate = delegate; + } + + + @Override + public ContentValues value() + { + ContentValues values = mDelegate.value(); + // just store the difference between due and start, if both are present, otherwise store null + values.put(TaskContract.Instances.INSTANCE_DURATION, + new Zipped<>( + new NullSafe<>(values.getAsLong(TaskContract.Instances.INSTANCE_START)), + new NullSafe<>(values.getAsLong(TaskContract.Instances.INSTANCE_DUE)), + (start, due) -> due - start) + .value(null)); + return values; + } +} diff --git a/opentasks-provider/src/main/java/org/dmfs/provider/tasks/processors/tasks/instancedata/Overridden.java b/opentasks-provider/src/main/java/org/dmfs/provider/tasks/processors/tasks/instancedata/Overridden.java new file mode 100644 index 000000000..f4b8cd407 --- /dev/null +++ b/opentasks-provider/src/main/java/org/dmfs/provider/tasks/processors/tasks/instancedata/Overridden.java @@ -0,0 +1,62 @@ +/* + * Copyright 2017 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.provider.tasks.processors.tasks.instancedata; + +import android.content.ContentValues; + +import org.dmfs.iterables.elementary.Seq; +import org.dmfs.jems.optional.decorators.Mapped; +import org.dmfs.jems.single.Single; +import org.dmfs.optional.NullSafe; +import org.dmfs.optional.Optional; +import org.dmfs.optional.adapters.FirstPresent; +import org.dmfs.rfc5545.DateTime; +import org.dmfs.tasks.contract.TaskContract; + + +/** + * A decorator for {@link Single}s of Instance {@link ContentValues} which populates the {@link TaskContract.Instances#INSTANCE_ORIGINAL_TIME} field based on + * the given {@link Optional} original start and the already populated {@link TaskContract.Instances#INSTANCE_START} and {@link + * TaskContract.Instances#INSTANCE_DUE} fields. + * + * @author Marten Gajda + */ +public final class Overridden implements Single<ContentValues> +{ + private final Optional<DateTime> mOriginalTime; + private final Single<ContentValues> mDelegate; + + + public Overridden(Optional<DateTime> originalTime, Single<ContentValues> delegate) + { + mOriginalTime = originalTime; + mDelegate = delegate; + } + + + @Override + public ContentValues value() + { + ContentValues values = mDelegate.value(); + values.put(TaskContract.Instances.INSTANCE_ORIGINAL_TIME, new FirstPresent<>(new Seq<>( + new Mapped<>(DateTime::getTimestamp, mOriginalTime), + new NullSafe<>(values.getAsLong(TaskContract.Instances.INSTANCE_START)), + new NullSafe<>(values.getAsLong(TaskContract.Instances.INSTANCE_DUE)))) + .value(null)); + return values; + } +} diff --git a/opentasks-provider/src/main/java/org/dmfs/provider/tasks/processors/tasks/instancedata/StartDated.java b/opentasks-provider/src/main/java/org/dmfs/provider/tasks/processors/tasks/instancedata/StartDated.java new file mode 100644 index 000000000..29bfeb99c --- /dev/null +++ b/opentasks-provider/src/main/java/org/dmfs/provider/tasks/processors/tasks/instancedata/StartDated.java @@ -0,0 +1,39 @@ +/* + * Copyright 2017 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.provider.tasks.processors.tasks.instancedata; + +import android.content.ContentValues; + +import org.dmfs.jems.single.Single; +import org.dmfs.jems.single.decorators.DelegatingSingle; +import org.dmfs.optional.Optional; +import org.dmfs.rfc5545.DateTime; +import org.dmfs.tasks.contract.TaskContract; + + +/** + * A decorator to a {@link Single} of {@link ContentValues} adding start data. + * + * @author Marten Gajda + */ +public final class StartDated extends DelegatingSingle<ContentValues> +{ + public StartDated(Optional<DateTime> start, Single<ContentValues> delegate) + { + super(new Dated(start, TaskContract.Instances.INSTANCE_START, TaskContract.Instances.INSTANCE_START_SORTING, delegate)); + } +} diff --git a/opentasks-provider/src/main/java/org/dmfs/provider/tasks/utils/WithTaskId.java b/opentasks-provider/src/main/java/org/dmfs/provider/tasks/processors/tasks/instancedata/TaskRelated.java similarity index 86% rename from opentasks-provider/src/main/java/org/dmfs/provider/tasks/utils/WithTaskId.java rename to opentasks-provider/src/main/java/org/dmfs/provider/tasks/processors/tasks/instancedata/TaskRelated.java index 114946395..6d7501cc5 100644 --- a/opentasks-provider/src/main/java/org/dmfs/provider/tasks/utils/WithTaskId.java +++ b/opentasks-provider/src/main/java/org/dmfs/provider/tasks/processors/tasks/instancedata/TaskRelated.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.dmfs.provider.tasks.utils; +package org.dmfs.provider.tasks.processors.tasks.instancedata; import android.content.ContentValues; @@ -27,13 +27,13 @@ * * @author Marten Gajda */ -public final class WithTaskId implements Single<ContentValues> +public final class TaskRelated implements Single<ContentValues> { private final long mTaskId; private final Single<ContentValues> mDelegate; - public WithTaskId(long taskId, Single<ContentValues> delegate) + public TaskRelated(long taskId, Single<ContentValues> delegate) { mTaskId = taskId; mDelegate = delegate; diff --git a/opentasks-provider/src/main/java/org/dmfs/provider/tasks/processors/tasks/instancedata/VanillaInstanceData.java b/opentasks-provider/src/main/java/org/dmfs/provider/tasks/processors/tasks/instancedata/VanillaInstanceData.java new file mode 100644 index 000000000..d3ac32643 --- /dev/null +++ b/opentasks-provider/src/main/java/org/dmfs/provider/tasks/processors/tasks/instancedata/VanillaInstanceData.java @@ -0,0 +1,45 @@ +/* + * Copyright 2017 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.provider.tasks.processors.tasks.instancedata; + +import android.content.ContentValues; + +import org.dmfs.jems.single.Single; +import org.dmfs.tasks.contract.TaskContract; + + +/** + * A {@link Single} of instance data {@link ContentValues}. It initializes most columns with {@code null} values, except for {@link + * TaskContract.Instances#TASK_ID} which is left out and {@link TaskContract.Instances#INSTANCE_ORIGINAL_TIME} which is initialized with {@code 0}. + * + * @author Marten Gajda + */ +public final class VanillaInstanceData implements Single<ContentValues> +{ + @Override + public ContentValues value() + { + ContentValues values = new ContentValues(6); + values.putNull(TaskContract.Instances.INSTANCE_START); + values.putNull(TaskContract.Instances.INSTANCE_START_SORTING); + values.putNull(TaskContract.Instances.INSTANCE_DUE); + values.putNull(TaskContract.Instances.INSTANCE_DUE_SORTING); + values.putNull(TaskContract.Instances.INSTANCE_DURATION); + values.put(TaskContract.Instances.INSTANCE_ORIGINAL_TIME, 0); + return values; + } +} diff --git a/opentasks-provider/src/main/java/org/dmfs/provider/tasks/utils/InstanceDateTimeData.java b/opentasks-provider/src/main/java/org/dmfs/provider/tasks/utils/InstanceDateTimeData.java deleted file mode 100644 index e0cb4e89e..000000000 --- a/opentasks-provider/src/main/java/org/dmfs/provider/tasks/utils/InstanceDateTimeData.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2017 dmfs GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.dmfs.provider.tasks.utils; - -import android.content.ContentValues; - -import org.dmfs.iterables.elementary.Seq; -import org.dmfs.jems.function.BiFunction; -import org.dmfs.jems.single.Single; -import org.dmfs.optional.Optional; -import org.dmfs.optional.adapters.FirstPresent; -import org.dmfs.optional.composite.Zipped; -import org.dmfs.rfc5545.DateTime; -import org.dmfs.rfc5545.Duration; -import org.dmfs.tasks.contract.TaskContract; - -import java.util.TimeZone; - - -/** - * A {@link Single} holding the date and time {@link ContentValues} of an instance. - * - * @author Marten Gajda - */ -public final class InstanceDateTimeData implements Single<ContentValues> -{ - private final Optional<DateTime> mOptionalStart; - private final Optional<DateTime> mOptionalDue; - private final Optional<Duration> mOptionalDuration; - private final Optional<DateTime> mOriginalStart; - - - public InstanceDateTimeData(Optional<DateTime> optionalStart, Optional<DateTime> optionalDue, Optional<Duration> optionalDuration, Optional<DateTime> originalStart) - { - mOptionalStart = optionalStart; - mOptionalDue = optionalDue; - mOptionalDuration = optionalDuration; - mOriginalStart = originalStart; - } - - - @Override - public ContentValues value() - { - ContentValues instanceValues = new ContentValues(); - TimeZone localTz = TimeZone.getDefault(); - - Optional<DateTime> effectiveDue = new FirstPresent<>( - new Seq<>(mOptionalDue, new Zipped<>(mOptionalStart, mOptionalDuration, new AddDurationBiFunction()))); - Optional<Long> effectiveDuration = new Zipped<>(mOptionalStart, effectiveDue, new DurationBiFunction()); - - putDateValue(instanceValues, mOptionalStart, TaskContract.Instances.INSTANCE_START, TaskContract.Instances.INSTANCE_START_SORTING, localTz); - putDateValue(instanceValues, effectiveDue, TaskContract.Instances.INSTANCE_DUE, TaskContract.Instances.INSTANCE_DUE_SORTING, localTz); - instanceValues.put(TaskContract.Instances.INSTANCE_DURATION, effectiveDuration.value(null)); - instanceValues.put(TaskContract.Instances.INSTANCE_ORIGINAL_TIME, - new FirstPresent<>(new Seq<>(mOriginalStart, mOptionalStart, effectiveDue)).value(new DateTime(0)).getTimestamp()); - - return instanceValues; - } - - - private void putDateValue(ContentValues values, Optional<DateTime> value, String timestampColumn, String sortingColumn, TimeZone localTimeZone) - { - if (value.isPresent()) - { - // add timestamp and sorting - DateTime dateTime = value.value(); - values.put(timestampColumn, dateTime.getTimestamp()); - values.put(sortingColumn, dateTime.isAllDay() ? dateTime.getInstance() : dateTime.shiftTimeZone(localTimeZone).getInstance()); - } - else - { - values.putNull(timestampColumn); - values.putNull(sortingColumn); - } - } - - - private static final class AddDurationBiFunction implements BiFunction<DateTime, Duration, DateTime> - { - @Override - public DateTime value(DateTime dateTime, Duration duration) - { - return dateTime.addDuration(duration); - } - } - - - private static final class DurationBiFunction implements BiFunction<DateTime, DateTime, Long> - { - @Override - public Long value(DateTime startDateTime, DateTime dueDateTime) - { - return dueDateTime.getTimestamp() - startDateTime.getTimestamp(); - } - } -} diff --git a/opentasks-provider/src/main/java/org/dmfs/provider/tasks/utils/InstanceValuesIterable.java b/opentasks-provider/src/main/java/org/dmfs/provider/tasks/utils/InstanceValuesIterable.java index 9a9bc3096..3c884b6b3 100644 --- a/opentasks-provider/src/main/java/org/dmfs/provider/tasks/utils/InstanceValuesIterable.java +++ b/opentasks-provider/src/main/java/org/dmfs/provider/tasks/utils/InstanceValuesIterable.java @@ -20,15 +20,18 @@ import org.dmfs.iterables.elementary.Seq; import org.dmfs.iterators.SingletonIterator; -import org.dmfs.jems.function.BiFunction; import org.dmfs.jems.single.Single; import org.dmfs.optional.NullSafe; import org.dmfs.optional.Optional; import org.dmfs.optional.adapters.FirstPresent; import org.dmfs.optional.composite.Zipped; import org.dmfs.provider.tasks.model.TaskAdapter; +import org.dmfs.provider.tasks.processors.tasks.instancedata.DueDated; +import org.dmfs.provider.tasks.processors.tasks.instancedata.Enduring; +import org.dmfs.provider.tasks.processors.tasks.instancedata.Overridden; +import org.dmfs.provider.tasks.processors.tasks.instancedata.StartDated; +import org.dmfs.provider.tasks.processors.tasks.instancedata.VanillaInstanceData; import org.dmfs.rfc5545.DateTime; -import org.dmfs.rfc5545.Duration; import java.util.Iterator; @@ -53,28 +56,18 @@ public InstanceValuesIterable(TaskAdapter taskAdapter) public Iterator<Single<ContentValues>> iterator() { Optional<DateTime> start = new NullSafe<>(mTaskAdapter.valueOf(TaskAdapter.DTSTART)); - Optional<DateTime> due = new NullSafe<>(mTaskAdapter.valueOf(TaskAdapter.DUE)); - - Optional<Duration> effectiveDuration = new FirstPresent<>( + // effective due is either the actual due, start + duration or absent + Optional<DateTime> effectiveDue = new FirstPresent<>( new Seq<>( - new NullSafe<>(mTaskAdapter.valueOf(TaskAdapter.DURATION)), - new Zipped<>(start, due, new DateTimeDurationBiFunction()))); - - // TODO: implement support for recurrence, for now we only return the first instance - return new SingletonIterator<Single<ContentValues>>( - new InstanceDateTimeData(start, due, effectiveDuration, new NullSafe<>(mTaskAdapter.valueOf(TaskAdapter.ORIGINAL_INSTANCE_TIME)))); - } + new NullSafe<>(mTaskAdapter.valueOf(TaskAdapter.DUE)), + new Zipped<>(start, new NullSafe<>(mTaskAdapter.valueOf(TaskAdapter.DURATION)), DateTime::addDuration))); + Single<ContentValues> baseData = new Enduring(new DueDated(effectiveDue, new StartDated(start, new VanillaInstanceData()))); - /** - * A {@link BiFunction} returning the duration between two {@link DateTime} values. - */ - private final static class DateTimeDurationBiFunction implements BiFunction<DateTime, DateTime, Duration> - { - @Override - public Duration value(DateTime start, DateTime due) - { - return new Duration(1, 0, (int) ((due.getTimestamp() - start.getTimestamp()) / 1000)); - } + // TODO: implement support for recurrence, for now we only return the first instance + return new SingletonIterator<>(mTaskAdapter.isRecurring() ? + new Overridden(new NullSafe<>(mTaskAdapter.valueOf(TaskAdapter.ORIGINAL_INSTANCE_TIME)), baseData) + : + baseData); } } diff --git a/opentasks-provider/src/main/java/org/dmfs/provider/tasks/utils/Range.java b/opentasks-provider/src/main/java/org/dmfs/provider/tasks/utils/Range.java index 52e144e5e..731e4243f 100644 --- a/opentasks-provider/src/main/java/org/dmfs/provider/tasks/utils/Range.java +++ b/opentasks-provider/src/main/java/org/dmfs/provider/tasks/utils/Range.java @@ -23,9 +23,12 @@ /** * An {@link Iterable} which iterates a range of numbers. + * <p> + * TODO: implement in jems * * @author Marten Gajda */ +@Deprecated public final class Range implements Iterable<Integer> { private final int mStart; diff --git a/opentasks-provider/src/main/java/org/dmfs/provider/tasks/utils/Zipped.java b/opentasks-provider/src/main/java/org/dmfs/provider/tasks/utils/Zipped.java new file mode 100644 index 000000000..06a5a3958 --- /dev/null +++ b/opentasks-provider/src/main/java/org/dmfs/provider/tasks/utils/Zipped.java @@ -0,0 +1,43 @@ +/* + * Copyright 2017 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.provider.tasks.utils; + +import org.dmfs.jems.function.BiFunction; +import org.dmfs.jems.optional.decorators.Mapped; +import org.dmfs.jems.single.Single; +import org.dmfs.jems.single.combined.Backed; +import org.dmfs.jems.single.decorators.DelegatingSingle; +import org.dmfs.optional.Optional; + + +/** + * Experimental {@link Single} which applies a {@link BiFunction} based on the presence of an {@link Optional}. + * <p> + * TODO: maybe a more appropriate name? + * <p> + * TODO: move to jems + * + * @author Marten Gajda + */ +@Deprecated +public final class Zipped<T> extends DelegatingSingle<T> +{ + public <V> Zipped(Optional<V> optionalValue, Single<T> delegate, BiFunction<V, T, T> function) + { + super(new Backed<T>(new Mapped<>(from -> function.value(from, delegate.value()), optionalValue), delegate)); + } +} diff --git a/opentasks-provider/src/test/java/org/dmfs/provider/tasks/processors/tasks/instancedata/DatedTest.java b/opentasks-provider/src/test/java/org/dmfs/provider/tasks/processors/tasks/instancedata/DatedTest.java new file mode 100644 index 000000000..5c8397a3d --- /dev/null +++ b/opentasks-provider/src/test/java/org/dmfs/provider/tasks/processors/tasks/instancedata/DatedTest.java @@ -0,0 +1,62 @@ +/* + * Copyright 2017 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.provider.tasks.processors.tasks.instancedata; + +import android.content.ContentValues; + +import org.dmfs.optional.Present; +import org.dmfs.provider.tasks.utils.ContentValuesWithLong; +import org.dmfs.rfc5545.DateTime; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import static org.dmfs.optional.Absent.absent; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + + +/** + * @author Marten Gajda + */ +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public class DatedTest +{ + + @Test + public void testAbsent() throws Exception + { + ContentValues instanceData = new Dated(absent(), "ts", "sorting", ContentValues::new).value(); + // this shouldn't really add any values and go by the "defaults" + assertThat(instanceData.size(), is(0)); + } + + + @Test + public void testPresent() throws Exception + { + DateTime start = DateTime.parse("Europe/Berlin", "20171208T125500"); + + ContentValues instanceData = new Dated(new Present<>(start), "ts", "sorting", ContentValues::new).value(); + + assertThat(instanceData, new ContentValuesWithLong("ts", start.getTimestamp())); + assertThat(instanceData, new ContentValuesWithLong("sorting", start.getInstance())); + assertThat(instanceData.size(), is(2)); + } +} \ No newline at end of file diff --git a/opentasks-provider/src/test/java/org/dmfs/provider/tasks/processors/tasks/instancedata/DueDatedTest.java b/opentasks-provider/src/test/java/org/dmfs/provider/tasks/processors/tasks/instancedata/DueDatedTest.java new file mode 100644 index 000000000..0db5b633f --- /dev/null +++ b/opentasks-provider/src/test/java/org/dmfs/provider/tasks/processors/tasks/instancedata/DueDatedTest.java @@ -0,0 +1,84 @@ +/* + * Copyright 2017 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.provider.tasks.processors.tasks.instancedata; + +import android.content.ContentValues; + +import org.dmfs.optional.Present; +import org.dmfs.provider.tasks.utils.ContentValuesWithLong; +import org.dmfs.rfc5545.DateTime; +import org.dmfs.tasks.contract.TaskContract; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import java.util.TimeZone; + +import static org.dmfs.optional.Absent.absent; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; + + +/** + * @author Marten Gajda + */ +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public class DueDatedTest +{ + + @Test + public void testNone() throws Exception + { + ContentValues instanceData = new DueDated(absent(), ContentValues::new).value(); + + assertThat(instanceData, new ContentValuesWithLong(TaskContract.Instances.INSTANCE_DUE, nullValue(Long.class))); + assertThat(instanceData, new ContentValuesWithLong(TaskContract.Instances.INSTANCE_DUE_SORTING, nullValue(Long.class))); + // this doesn't actually add anything, the ContentValues are expected to contain null values. + assertThat(instanceData.size(), is(0)); + } + + + @Test + public void testStartEurope() throws Exception + { + DateTime start = DateTime.parse("Europe/Berlin", "20171208T125500"); + + ContentValues instanceData = new DueDated(new Present<>(start), ContentValues::new).value(); + + assertThat(instanceData, new ContentValuesWithLong(TaskContract.Instances.INSTANCE_DUE, start.getTimestamp())); + assertThat(instanceData, + new ContentValuesWithLong(TaskContract.Instances.INSTANCE_DUE_SORTING, start.shiftTimeZone(TimeZone.getDefault()).getInstance())); + assertThat(instanceData.size(), is(2)); + } + + + @Test + public void testStartAmerica() throws Exception + { + DateTime start = DateTime.parse("America/New_York", "20171208T125500"); + + ContentValues instanceData = new DueDated(new Present<>(start), ContentValues::new).value(); + + assertThat(instanceData, new ContentValuesWithLong(TaskContract.Instances.INSTANCE_DUE, start.getTimestamp())); + assertThat(instanceData, + new ContentValuesWithLong(TaskContract.Instances.INSTANCE_DUE_SORTING, start.shiftTimeZone(TimeZone.getDefault()).getInstance())); + assertThat(instanceData.size(), is(2)); + } +} \ No newline at end of file diff --git a/opentasks-provider/src/test/java/org/dmfs/provider/tasks/processors/tasks/instancedata/EnduringTest.java b/opentasks-provider/src/test/java/org/dmfs/provider/tasks/processors/tasks/instancedata/EnduringTest.java new file mode 100644 index 000000000..5c7b7e5eb --- /dev/null +++ b/opentasks-provider/src/test/java/org/dmfs/provider/tasks/processors/tasks/instancedata/EnduringTest.java @@ -0,0 +1,80 @@ +/* + * Copyright 2017 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.provider.tasks.processors.tasks.instancedata; + +import android.content.ContentValues; + +import org.dmfs.provider.tasks.utils.ContentValuesWithLong; +import org.dmfs.tasks.contract.TaskContract; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import static org.dmfs.jems.hamcrest.matchers.SingleMatcher.hasValue; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; + + +/** + * @author Marten Gajda + */ +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public class EnduringTest +{ + @Test + public void testNoValue() throws Exception + { + assertThat(new Enduring(ContentValues::new), hasValue(new ContentValuesWithLong(TaskContract.Instances.INSTANCE_DURATION, nullValue(Long.class)))); + assertThat(new Enduring(ContentValues::new).value().size(), is(1)); + } + + + @Test + public void testStartValue() throws Exception + { + ContentValues values = new ContentValues(1); + values.put(TaskContract.Instances.INSTANCE_START, 10); + assertThat(new Enduring(() -> new ContentValues(values)), + hasValue(new ContentValuesWithLong(TaskContract.Instances.INSTANCE_DURATION, nullValue(Long.class)))); + assertThat(new Enduring(() -> new ContentValues(values)).value().size(), is(2)); + } + + + @Test + public void testDueValue() throws Exception + { + ContentValues values = new ContentValues(1); + values.put(TaskContract.Instances.INSTANCE_DUE, 10); + assertThat(new Enduring(() -> new ContentValues(values)), + hasValue(new ContentValuesWithLong(TaskContract.Instances.INSTANCE_DURATION, nullValue(Long.class)))); + assertThat(new Enduring(() -> new ContentValues(values)).value().size(), is(2)); + } + + + @Test + public void testStartDueValue() throws Exception + { + ContentValues values = new ContentValues(2); + values.put(TaskContract.Instances.INSTANCE_START, 1); + values.put(TaskContract.Instances.INSTANCE_DUE, 10); + assertThat(new Enduring(() -> new ContentValues(values)), hasValue(new ContentValuesWithLong(TaskContract.Instances.INSTANCE_DURATION, 9))); + assertThat(new Enduring(() -> new ContentValues(values)).value().size(), is(3)); + } +} \ No newline at end of file diff --git a/opentasks-provider/src/test/java/org/dmfs/provider/tasks/processors/tasks/instancedata/OverriddenTest.java b/opentasks-provider/src/test/java/org/dmfs/provider/tasks/processors/tasks/instancedata/OverriddenTest.java new file mode 100644 index 000000000..3882bc523 --- /dev/null +++ b/opentasks-provider/src/test/java/org/dmfs/provider/tasks/processors/tasks/instancedata/OverriddenTest.java @@ -0,0 +1,110 @@ +/* + * Copyright 2017 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.provider.tasks.processors.tasks.instancedata; + +import android.content.ContentValues; + +import org.dmfs.optional.Present; +import org.dmfs.provider.tasks.utils.ContentValuesWithLong; +import org.dmfs.rfc5545.DateTime; +import org.dmfs.tasks.contract.TaskContract; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import static org.dmfs.optional.Absent.absent; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; + + +/** + * @author Marten Gajda + */ +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public class OverriddenTest +{ + @Test + public void testAbsent() throws Exception + { + ContentValues instanceData = new Overridden(absent(), ContentValues::new).value(); + assertThat(instanceData, new ContentValuesWithLong(TaskContract.Instances.INSTANCE_ORIGINAL_TIME, nullValue(Long.class))); + assertThat(instanceData.size(), is(1)); + } + + + @Test + public void testAbsentWithStart() throws Exception + { + ContentValues values = new ContentValues(); + values.put(TaskContract.Instances.INSTANCE_START, 10); + + ContentValues instanceData = new Overridden(absent(), () -> new ContentValues(values)).value(); + assertThat(instanceData, new ContentValuesWithLong(TaskContract.Instances.INSTANCE_ORIGINAL_TIME, 10)); + assertThat(instanceData.size(), is(2)); + } + + + @Test + public void testAbsentWithDue() throws Exception + { + ContentValues values = new ContentValues(); + values.put(TaskContract.Instances.INSTANCE_DUE, 20); + + ContentValues instanceData = new Overridden(absent(), () -> new ContentValues(values)).value(); + assertThat(instanceData, new ContentValuesWithLong(TaskContract.Instances.INSTANCE_ORIGINAL_TIME, 20)); + assertThat(instanceData.size(), is(2)); + } + + + @Test + public void testAbsentWithStartAndDue() throws Exception + { + ContentValues values = new ContentValues(); + values.put(TaskContract.Instances.INSTANCE_START, 10); + values.put(TaskContract.Instances.INSTANCE_DUE, 20); + + ContentValues instanceData = new Overridden(absent(), () -> new ContentValues(values)).value(); + assertThat(instanceData, new ContentValuesWithLong(TaskContract.Instances.INSTANCE_ORIGINAL_TIME, 10)); + assertThat(instanceData.size(), is(3)); + } + + + @Test + public void testPresent() throws Exception + { + + ContentValues instanceData = new Overridden(new Present<>(new DateTime(40)), ContentValues::new).value(); + assertThat(instanceData, new ContentValuesWithLong(TaskContract.Instances.INSTANCE_ORIGINAL_TIME, 40)); + assertThat(instanceData.size(), is(1)); + } + + + @Test + public void testPresentWithStartAndDue() throws Exception + { + ContentValues values = new ContentValues(); + values.put(TaskContract.Instances.INSTANCE_START, 10); + values.put(TaskContract.Instances.INSTANCE_DUE, 20); + + ContentValues instanceData = new Overridden(new Present<>(new DateTime(40)), () -> new ContentValues(values)).value(); + assertThat(instanceData, new ContentValuesWithLong(TaskContract.Instances.INSTANCE_ORIGINAL_TIME, 40)); + assertThat(instanceData.size(), is(3)); + } +} \ No newline at end of file diff --git a/opentasks-provider/src/test/java/org/dmfs/provider/tasks/processors/tasks/instancedata/StartDatedTest.java b/opentasks-provider/src/test/java/org/dmfs/provider/tasks/processors/tasks/instancedata/StartDatedTest.java new file mode 100644 index 000000000..991af0862 --- /dev/null +++ b/opentasks-provider/src/test/java/org/dmfs/provider/tasks/processors/tasks/instancedata/StartDatedTest.java @@ -0,0 +1,84 @@ +/* + * Copyright 2017 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.provider.tasks.processors.tasks.instancedata; + +import android.content.ContentValues; + +import org.dmfs.optional.Present; +import org.dmfs.provider.tasks.utils.ContentValuesWithLong; +import org.dmfs.rfc5545.DateTime; +import org.dmfs.tasks.contract.TaskContract; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import java.util.TimeZone; + +import static org.dmfs.optional.Absent.absent; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; + + +/** + * @author Marten Gajda + */ +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public class StartDatedTest +{ + + @Test + public void testNone() throws Exception + { + ContentValues instanceData = new StartDated(absent(), ContentValues::new).value(); + + assertThat(instanceData, new ContentValuesWithLong(TaskContract.Instances.INSTANCE_START, nullValue(Long.class))); + assertThat(instanceData, new ContentValuesWithLong(TaskContract.Instances.INSTANCE_START_SORTING, nullValue(Long.class))); + // this doesn't actually add anything, the ContentValues are expected to contain null values. + assertThat(instanceData.size(), is(0)); + } + + + @Test + public void testStartEurope() throws Exception + { + DateTime start = DateTime.parse("Europe/Berlin", "20171208T125500"); + + ContentValues instanceData = new StartDated(new Present<>(start), ContentValues::new).value(); + + assertThat(instanceData, new ContentValuesWithLong(TaskContract.Instances.INSTANCE_START, start.getTimestamp())); + assertThat(instanceData, + new ContentValuesWithLong(TaskContract.Instances.INSTANCE_START_SORTING, start.shiftTimeZone(TimeZone.getDefault()).getInstance())); + assertThat(instanceData.size(), is(2)); + } + + + @Test + public void testStartAmerica() throws Exception + { + DateTime start = DateTime.parse("America/New_York", "20171208T125500"); + + ContentValues instanceData = new StartDated(new Present<>(start), ContentValues::new).value(); + + assertThat(instanceData, new ContentValuesWithLong(TaskContract.Instances.INSTANCE_START, start.getTimestamp())); + assertThat(instanceData, + new ContentValuesWithLong(TaskContract.Instances.INSTANCE_START_SORTING, start.shiftTimeZone(TimeZone.getDefault()).getInstance())); + assertThat(instanceData.size(), is(2)); + } +} \ No newline at end of file diff --git a/opentasks-provider/src/test/java/org/dmfs/provider/tasks/processors/tasks/instancedata/TaskRelatedTest.java b/opentasks-provider/src/test/java/org/dmfs/provider/tasks/processors/tasks/instancedata/TaskRelatedTest.java new file mode 100644 index 000000000..3f4ae122e --- /dev/null +++ b/opentasks-provider/src/test/java/org/dmfs/provider/tasks/processors/tasks/instancedata/TaskRelatedTest.java @@ -0,0 +1,44 @@ +/* + * Copyright 2017 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.provider.tasks.processors.tasks.instancedata; + +import android.content.ContentValues; + +import org.dmfs.provider.tasks.utils.ContentValuesWithLong; +import org.dmfs.tasks.contract.TaskContract; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import static org.dmfs.jems.hamcrest.matchers.SingleMatcher.hasValue; +import static org.junit.Assert.assertThat; + + +/** + * @author Marten Gajda + */ +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public class TaskRelatedTest +{ + @Test + public void testValue() throws Exception + { + assertThat(new TaskRelated(123, ContentValues::new), hasValue(new ContentValuesWithLong(TaskContract.Instances.TASK_ID, 123))); + } +} \ No newline at end of file diff --git a/opentasks-provider/src/test/java/org/dmfs/provider/tasks/processors/tasks/instancedata/VanillaInstanceDataTest.java b/opentasks-provider/src/test/java/org/dmfs/provider/tasks/processors/tasks/instancedata/VanillaInstanceDataTest.java new file mode 100644 index 000000000..f3a5678fe --- /dev/null +++ b/opentasks-provider/src/test/java/org/dmfs/provider/tasks/processors/tasks/instancedata/VanillaInstanceDataTest.java @@ -0,0 +1,52 @@ +/* + * Copyright 2017 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.provider.tasks.processors.tasks.instancedata; + +import android.content.ContentValues; + +import org.dmfs.tasks.contract.TaskContract; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; + + +/** + * @author Marten Gajda + */ +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public class VanillaInstanceDataTest +{ + @Test + public void testValue() throws Exception + { + ContentValues values = new VanillaInstanceData().value(); + assertThat(values.get(TaskContract.Instances.INSTANCE_START), nullValue()); + assertThat(values.get(TaskContract.Instances.INSTANCE_START_SORTING), nullValue()); + assertThat(values.get(TaskContract.Instances.INSTANCE_DUE), nullValue()); + assertThat(values.get(TaskContract.Instances.INSTANCE_DUE_SORTING), nullValue()); + assertThat(values.get(TaskContract.Instances.INSTANCE_DURATION), nullValue()); + assertThat(values.get(TaskContract.Instances.INSTANCE_ORIGINAL_TIME), is(0)); + assertThat(values.size(), is(6)); + } + +} \ No newline at end of file diff --git a/opentasks-provider/src/test/java/org/dmfs/provider/tasks/utils/ContentValuesWithLong.java b/opentasks-provider/src/test/java/org/dmfs/provider/tasks/utils/ContentValuesWithLong.java new file mode 100644 index 000000000..cf03faded --- /dev/null +++ b/opentasks-provider/src/test/java/org/dmfs/provider/tasks/utils/ContentValuesWithLong.java @@ -0,0 +1,57 @@ +/* + * Copyright 2017 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.provider.tasks.utils; + +import android.content.ContentValues; + +import org.hamcrest.FeatureMatcher; +import org.hamcrest.Matcher; + +import static org.hamcrest.Matchers.is; + + +/** + * A {@link Matcher} to test if {@link ContentValues} contain a specific Long value. + * <p> + * TODO: can we convert that into a more generic {@link ContentValues} matcher? It might be useful in other places. + * + * TODO: also consider moving this to "Test-Bolts" + */ +public final class ContentValuesWithLong extends FeatureMatcher<ContentValues, Long> +{ + private final String mKey; + + + public ContentValuesWithLong(String valueKey, long value) + { + this(valueKey, is(value)); + } + + + public ContentValuesWithLong(String valueKey, Matcher<Long> matcher) + { + super(matcher, "Long value " + valueKey, "Long value " + valueKey); + mKey = valueKey; + } + + + @Override + protected Long featureValueOf(ContentValues actual) + { + return actual.getAsLong(mKey); + } +} diff --git a/opentasks-provider/src/test/java/org/dmfs/provider/tasks/utils/InstanceDateTimeDataTest.java b/opentasks-provider/src/test/java/org/dmfs/provider/tasks/utils/InstanceDateTimeDataTest.java deleted file mode 100644 index 934d341e5..000000000 --- a/opentasks-provider/src/test/java/org/dmfs/provider/tasks/utils/InstanceDateTimeDataTest.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright 2017 dmfs GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.dmfs.provider.tasks.utils; - -import android.content.ContentValues; - -import org.dmfs.optional.Absent; -import org.dmfs.optional.Present; -import org.dmfs.rfc5545.DateTime; -import org.dmfs.rfc5545.Duration; -import org.dmfs.tasks.contract.TaskContract; -import org.hamcrest.FeatureMatcher; -import org.hamcrest.Matcher; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; - -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.nullValue; -import static org.junit.Assert.assertThat; - - -/** - * @author Marten Gajda - */ -@RunWith(RobolectricTestRunner.class) -@Config(manifest = Config.NONE) -public class InstanceDateTimeDataTest -{ - - @Test - public void testNone() throws Exception - { - ContentValues instanceData = new InstanceDateTimeData(Absent.<DateTime>absent(), Absent.<DateTime>absent(), Absent.<Duration>absent(), - Absent.<DateTime>absent()).value(); - - assertThat(instanceData, new Contains(TaskContract.Instances.INSTANCE_START, nullValue(Long.class))); - assertThat(instanceData, new Contains(TaskContract.Instances.INSTANCE_START_SORTING, nullValue(Long.class))); - assertThat(instanceData, new Contains(TaskContract.Instances.INSTANCE_DUE, nullValue(Long.class))); - assertThat(instanceData, new Contains(TaskContract.Instances.INSTANCE_DUE_SORTING, nullValue(Long.class))); - assertThat(instanceData, new Contains(TaskContract.Instances.INSTANCE_DURATION, nullValue(Long.class))); - assertThat(instanceData, new Contains(TaskContract.Instances.INSTANCE_ORIGINAL_TIME, 0)); - } - - - @Test - public void testStart() throws Exception - { - DateTime start = DateTime.parse("Europe/Berlin", "20171208T125500"); - - ContentValues instanceData = new InstanceDateTimeData(new Present<>(start), Absent.<DateTime>absent(), Absent.<Duration>absent(), - new Present<>(start)).value(); - - assertThat(instanceData, new Contains(TaskContract.Instances.INSTANCE_START, start.getTimestamp())); - assertThat(instanceData, new Contains(TaskContract.Instances.INSTANCE_START_SORTING, start.getInstance())); - assertThat(instanceData, new Contains(TaskContract.Instances.INSTANCE_DUE, nullValue(Long.class))); - assertThat(instanceData, new Contains(TaskContract.Instances.INSTANCE_DUE_SORTING, nullValue(Long.class))); - assertThat(instanceData, new Contains(TaskContract.Instances.INSTANCE_DURATION, nullValue(Long.class))); - assertThat(instanceData, new Contains(TaskContract.Instances.INSTANCE_ORIGINAL_TIME, start.getTimestamp())); - } - - - @Test - public void testStartDuration() throws Exception - { - DateTime start = DateTime.parse("Europe/Berlin", "20171208T125500"); - DateTime due = DateTime.parse("Europe/Berlin", "20171208T155500"); - Duration duration = Duration.parse("PT3H"); - - ContentValues instanceData = new InstanceDateTimeData(new Present<>(start), Absent.<DateTime>absent(), new Present<>(duration), - new Present<>(start)).value(); - - assertThat(instanceData, new Contains(TaskContract.Instances.INSTANCE_START, start.getTimestamp())); - assertThat(instanceData, new Contains(TaskContract.Instances.INSTANCE_START_SORTING, start.getInstance())); - assertThat(instanceData, new Contains(TaskContract.Instances.INSTANCE_DUE, due.getTimestamp())); - assertThat(instanceData, new Contains(TaskContract.Instances.INSTANCE_DUE_SORTING, due.getInstance())); - assertThat(instanceData, new Contains(TaskContract.Instances.INSTANCE_DURATION, duration.toMillis())); - assertThat(instanceData, new Contains(TaskContract.Instances.INSTANCE_ORIGINAL_TIME, start.getTimestamp())); - } - - - @Test - public void testStartDue() throws Exception - { - DateTime start = DateTime.parse("Europe/Berlin", "20171208T125500"); - DateTime due = DateTime.parse("Europe/Berlin", "20171208T155500"); - Duration duration = Duration.parse("PT3H"); - - ContentValues instanceData = new InstanceDateTimeData(new Present<>(start), new Present<>(due), Absent.<Duration>absent(), - new Present<>(start)).value(); - - assertThat(instanceData, new Contains(TaskContract.Instances.INSTANCE_START, start.getTimestamp())); - assertThat(instanceData, new Contains(TaskContract.Instances.INSTANCE_START_SORTING, start.getInstance())); - assertThat(instanceData, new Contains(TaskContract.Instances.INSTANCE_DUE, due.getTimestamp())); - assertThat(instanceData, new Contains(TaskContract.Instances.INSTANCE_DUE_SORTING, due.getInstance())); - assertThat(instanceData, new Contains(TaskContract.Instances.INSTANCE_DURATION, duration.toMillis())); - assertThat(instanceData, new Contains(TaskContract.Instances.INSTANCE_ORIGINAL_TIME, start.getTimestamp())); - } - - - @Test - public void testStartDueOriginal() throws Exception - { - DateTime start = DateTime.parse("Europe/Berlin", "20171208T125500"); - DateTime due = DateTime.parse("Europe/Berlin", "20171208T155500"); - Duration duration = Duration.parse("PT3H"); - DateTime original = DateTime.parse("Europe/Berlin", "20171210T155500"); - - ContentValues instanceData = new InstanceDateTimeData(new Present<>(start), new Present<>(due), Absent.<Duration>absent(), - new Present<>(original)).value(); - - assertThat(instanceData, new Contains(TaskContract.Instances.INSTANCE_START, start.getTimestamp())); - assertThat(instanceData, new Contains(TaskContract.Instances.INSTANCE_START_SORTING, start.getInstance())); - assertThat(instanceData, new Contains(TaskContract.Instances.INSTANCE_DUE, due.getTimestamp())); - assertThat(instanceData, new Contains(TaskContract.Instances.INSTANCE_DUE_SORTING, due.getInstance())); - assertThat(instanceData, new Contains(TaskContract.Instances.INSTANCE_DURATION, duration.toMillis())); - assertThat(instanceData, new Contains(TaskContract.Instances.INSTANCE_ORIGINAL_TIME, original.getTimestamp())); - } - - - @Test - public void testStartDueAbsentOriginal() throws Exception - { - DateTime start = DateTime.parse("Europe/Berlin", "20171208T125500"); - DateTime due = DateTime.parse("Europe/Berlin", "20171208T155500"); - Duration duration = Duration.parse("PT3H"); - - ContentValues instanceData = new InstanceDateTimeData(new Present<>(start), new Present<>(due), Absent.<Duration>absent(), - Absent.<DateTime>absent()).value(); - - assertThat(instanceData, new Contains(TaskContract.Instances.INSTANCE_START, start.getTimestamp())); - assertThat(instanceData, new Contains(TaskContract.Instances.INSTANCE_START_SORTING, start.getInstance())); - assertThat(instanceData, new Contains(TaskContract.Instances.INSTANCE_DUE, due.getTimestamp())); - assertThat(instanceData, new Contains(TaskContract.Instances.INSTANCE_DUE_SORTING, due.getInstance())); - assertThat(instanceData, new Contains(TaskContract.Instances.INSTANCE_DURATION, duration.toMillis())); - assertThat(instanceData, new Contains(TaskContract.Instances.INSTANCE_ORIGINAL_TIME, start.getTimestamp())); - } - - - /** - * A {@link Matcher} to test if {@link ContentValues} contain a specific Long value. - * <p> - * TODO: can we convert that into a more generic {@link ContentValues} matcher? It might be useful in other places. - */ - private final class Contains extends FeatureMatcher<ContentValues, Long> - { - private final String mKey; - - - public Contains(String valueKey, long value) - { - this(valueKey, is(value)); - } - - - public Contains(String valueKey, Matcher<Long> matcher) - { - super(matcher, "Long value " + valueKey, "Long value " + valueKey); - mKey = valueKey; - } - - - @Override - protected Long featureValueOf(ContentValues actual) - { - return actual.getAsLong(mKey); - } - } -} \ No newline at end of file diff --git a/opentasks-provider/src/test/java/org/dmfs/provider/tasks/utils/ZippedTest.java b/opentasks-provider/src/test/java/org/dmfs/provider/tasks/utils/ZippedTest.java new file mode 100644 index 000000000..1ee7ac873 --- /dev/null +++ b/opentasks-provider/src/test/java/org/dmfs/provider/tasks/utils/ZippedTest.java @@ -0,0 +1,56 @@ +/* + * Copyright 2017 dmfs GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dmfs.provider.tasks.utils; + +import org.dmfs.jems.function.BiFunction; +import org.dmfs.jems.single.elementary.ValueSingle; +import org.dmfs.optional.Present; +import org.junit.Test; + +import static org.dmfs.jems.hamcrest.matchers.SingleMatcher.hasValue; +import static org.dmfs.jems.mockito.doubles.TestDoubles.dummy; +import static org.dmfs.jems.mockito.doubles.TestDoubles.failingMock; +import static org.dmfs.optional.Absent.absent; +import static org.hamcrest.Matchers.sameInstance; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.doReturn; + + +/** + * @author Marten Gajda + */ +public class ZippedTest +{ + @Test + public void testPresent() + { + Object dummyPresentValue = new Object(); + Object dummySingleValue = new Object(); + Object dummyResult = new Object(); + BiFunction<Object, Object, Object> mockFunction = failingMock(BiFunction.class); + doReturn(dummyResult).when(mockFunction).value(dummyPresentValue, dummySingleValue); + assertThat(new Zipped<>(new Present<>(dummyPresentValue), new ValueSingle<>(dummySingleValue), mockFunction), hasValue(sameInstance(dummyResult))); + } + + + @Test + public void testAbsent() + { + Object dummyObject = new Object(); + assertThat(new Zipped<>(absent(), new ValueSingle<>(dummyObject), dummy(BiFunction.class)), hasValue(sameInstance(dummyObject))); + } +} \ No newline at end of file