diff --git a/android/build.gradle b/android/build.gradle index ac31203b77..103961f163 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -100,6 +100,11 @@ dependencies { implementation 'androidx.preference:preference:1.2.0' implementation 'androidx.fragment:fragment:1.4.1' implementation 'androidx.recyclerview:recyclerview:1.2.1' + + // Test Dependencies + testImplementation "junit:junit:$jUnitVersion" + testImplementation "org.mockito:mockito-core:$mockitoVersion" + testImplementation "org.mockito:mockito-inline:$mockitoVersion" } def run(cmd) { @@ -219,6 +224,11 @@ android { jniLibs.srcDirs = [android.getNdkDirectory().toString() + '/sources/third_party/vulkan/src/build-android/jniLibs'] } + sourceSets.test { + java.srcDirs = ['tests/java'] + res.srcDirs = ['tests/resources'] + } + flavorDimensions "default" productFlavors { diff --git a/android/gradle.properties b/android/gradle.properties index 17d04cffd4..8b96ac3910 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -3,6 +3,8 @@ propTargetSdkVersion=31 propCompileSdkVersion=31 propBuildToolsVersion=32.0.0 propMultiDexVersion=2.0.1 +jUnitVersion=4.13.2 +mockitoVersion=4.2.0 org.gradle.caching=true org.gradle.jvmargs=-Xmx1024m -Xms256m diff --git a/android/src/com/mapswithme/maps/editor/data/TimeFormatUtils.java b/android/src/com/mapswithme/maps/editor/data/TimeFormatUtils.java index 64e0c77782..3be36c3c26 100644 --- a/android/src/com/mapswithme/maps/editor/data/TimeFormatUtils.java +++ b/android/src/com/mapswithme/maps/editor/data/TimeFormatUtils.java @@ -39,9 +39,17 @@ public class TimeFormatUtils return sShortWeekdays[day]; } - public static String formatWeekdays(@NonNull Timetable timetable) + public static String formatWeekdaysRange(int startWeekDay, int endWeekDay) { refreshWithCurrentLocale(); + if (startWeekDay == endWeekDay) + return sShortWeekdays[startWeekDay]; + else + return sShortWeekdays[startWeekDay] + "-" + sShortWeekdays[endWeekDay]; + } + + public static String formatWeekdays(@NonNull Timetable timetable) + { return formatWeekdays(timetable.weekdays); } @@ -50,6 +58,7 @@ public class TimeFormatUtils if (weekdays.length == 0) return ""; + refreshWithCurrentLocale(); final StringBuilder builder = new StringBuilder(sShortWeekdays[weekdays[0]]); boolean iteratingRange; for (int i = 1; i < weekdays.length; ) diff --git a/android/src/com/mapswithme/maps/widget/placepage/PlaceOpeningHoursAdapter.java b/android/src/com/mapswithme/maps/widget/placepage/PlaceOpeningHoursAdapter.java index 5483bf72e0..80bfd25839 100644 --- a/android/src/com/mapswithme/maps/widget/placepage/PlaceOpeningHoursAdapter.java +++ b/android/src/com/mapswithme/maps/widget/placepage/PlaceOpeningHoursAdapter.java @@ -1,5 +1,6 @@ package com.mapswithme.maps.widget.placepage; +import android.util.ArraySet; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -9,6 +10,7 @@ import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import com.mapswithme.maps.R; import static com.mapswithme.maps.editor.data.TimeFormatUtils.formatWeekdays; +import static com.mapswithme.maps.editor.data.TimeFormatUtils.formatWeekdaysRange; import static com.mapswithme.maps.editor.data.TimeFormatUtils.formatNonBusinessTime; import com.mapswithme.maps.editor.data.Timespan; @@ -17,46 +19,84 @@ import com.mapswithme.util.UiUtils; import java.util.ArrayList; import java.util.Arrays; +import java.util.Calendar; +import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; public class PlaceOpeningHoursAdapter extends RecyclerView.Adapter { - private Timetable[] mTimetables = {}; - private int[] closedDays = null; + private List mWeekSchedule = Collections.emptyList(); public PlaceOpeningHoursAdapter() {} - public PlaceOpeningHoursAdapter(Timetable[] timetables) + public PlaceOpeningHoursAdapter(Timetable[] timetables, int firstDayOfWeek) { - setTimetables(timetables); + setTimetables(timetables, firstDayOfWeek); } - public void setTimetables(Timetable[] timetables) + public void setTimetables(Timetable[] timetables, int firstDayOfWeek) { - mTimetables = timetables; - closedDays = findUnhandledDays(timetables); - notifyDataSetChanged(); - } + int[] weekDays = null; + if (firstDayOfWeek == Calendar.SUNDAY) + weekDays = new int[]{1, 2, 3, 4, 5, 6, 7}; + else + weekDays = new int[]{2, 3, 4, 5, 6, 7, 1}; - private int[] findUnhandledDays(Timetable[] timetables) - { - List unhandledDays = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7)); + final List scheduleData = new ArrayList<>(); - for (Timetable tt : timetables) { - for (int weekDay : tt.weekdays) { - unhandledDays.remove(Integer.valueOf(weekDay)); + // timetables array contains only working days. We need to fill non working gaps + for (int idx=0; idx < weekDays.length; idx++) + { + int weekDay = weekDays[idx]; + + Timetable tt = findScheduleForWeekDay(timetables, weekDay); + if (tt != null) + { + int startWeekDay = weekDays[idx]; + while(idx < weekDays.length && tt.containsWeekday(weekDays[idx])) + idx++; + + idx--; + int endWeekDay = weekDays[idx]; + scheduleData.add(new WeekScheduleData(startWeekDay, endWeekDay, false, tt)); + } + else + { + int startIdx = idx; + while (idx+1 < weekDays.length) + { + Timetable tt2 = findScheduleForWeekDay(timetables, weekDays[idx+1]); + if (tt2 != null) + break; + idx ++; + } + + scheduleData.add(new WeekScheduleData(weekDays[startIdx], weekDays[idx], true, null)); } } - if (unhandledDays.isEmpty()) - return null; + mWeekSchedule = scheduleData; - // Convert List to int[]. - int[] days = new int[unhandledDays.size()]; - for (int i = 0; i < days.length; i++) - days[i] = unhandledDays.get(i); + notifyDataSetChanged(); + } - return days; + public static Timetable findScheduleForWeekDay(Timetable[] tables, int weekDay) + { + for(Timetable tt : tables) + if (tt.containsWeekday(weekDay)) + return tt; + + return null; + } + + public static int[] createRange(int start, int end) + { + int[] result = new int[end-start+1]; + for (int i=start; i <= end; i++) + result[i-start] = i; + return result; } @NonNull @@ -70,26 +110,25 @@ public class PlaceOpeningHoursAdapter extends RecyclerView.Adapter mTimetables.length || position < 0) + if (mWeekSchedule == null || position >= mWeekSchedule.size() || position < 0) return; - if (position == mTimetables.length) + final WeekScheduleData schedule = mWeekSchedule.get(position); + + if (schedule.isClosed) { - if (closedDays == null) - return; - holder.setWeekdays(formatWeekdays(closedDays)); + holder.setWeekdays(formatWeekdaysRange(schedule.startWeekDay, schedule.endWeekDay)); holder.setOpenTime(holder.itemView.getResources().getString(R.string.day_off)); holder.hideNonBusinessTime(); return; } - final Timetable tt = mTimetables[position]; + final Timetable tt = schedule.timetable; - final String weekdays = formatWeekdays(tt); String workingTime = tt.isFullday ? holder.itemView.getResources().getString(R.string.editor_time_allday) : tt.workingTimespan.toWideString(); - holder.setWeekdays(weekdays); + holder.setWeekdays(formatWeekdaysRange(schedule.startWeekDay, schedule.endWeekDay)); holder.setOpenTime(workingTime); final Timespan[] closedTime = tt.closedTimespans; @@ -107,7 +146,26 @@ public class PlaceOpeningHoursAdapter extends RecyclerView.Adapter schedule = (List)mWeekScheduleField.get(adapter); + assertNotNull(schedule); + assertEquals(3, schedule.size()); + assertTrue(schedule.get(0).isClosed); + assertEquals(schedule.get(0).startWeekDay, 1); + assertEquals(schedule.get(0).endWeekDay, 1); + assertFalse(schedule.get(1).isClosed); + assertFalse(schedule.get(2).isClosed); + } + + @Test + public void test_single_closed_day_monday() throws IllegalAccessException + { + // opening_hours = "Mo-Fr 09:00-18:00; Sa 12:00-14:00" + Timetable[] timetables = new Timetable[2]; + timetables[0] = new Timetable(new Timespan(new HoursMinutes(9,0, true), + new HoursMinutes(18,0, true)), + new Timespan[0], + false, + new int[]{2, 3, 4, 5, 6}); + + timetables[1] = new Timetable(new Timespan(new HoursMinutes(12,0, true), + new HoursMinutes(14,0, true)), + new Timespan[0], + false, + new int[]{7}); + + adapter.setTimetables(timetables, Calendar.MONDAY); + + /* Expected parsed schedule: + * 0 - Mo-Fr, open + * 1 - Sa, open + * 2 - Su, closed + * */ + + List schedule = (List)mWeekScheduleField.get(adapter); + assertNotNull(schedule); + assertEquals(3, schedule.size()); + assertFalse(schedule.get(0).isClosed); // Mo-Fr - open + assertFalse(schedule.get(1).isClosed); // Sa - open + assertTrue(schedule.get(2).isClosed); // Su - closed + assertEquals(schedule.get(2).startWeekDay, 1); + assertEquals(schedule.get(2).endWeekDay, 1); + } + + @Test + public void test_multiple_closed_day_monday() throws IllegalAccessException + { + // opening_hours = "Mo 09:00-18:00; We 10:00-18:00; Fr 11:00-18:00" + Timetable[] timetables = new Timetable[3]; + timetables[0] = new Timetable(new Timespan(new HoursMinutes(9,0, true), + new HoursMinutes(18,0, true)), + new Timespan[0], + false, + new int[]{2}); + + timetables[1] = new Timetable(new Timespan(new HoursMinutes(10,0, true), + new HoursMinutes(18,0, true)), + new Timespan[0], + false, + new int[]{4}); + + timetables[2] = new Timetable(new Timespan(new HoursMinutes(11,0, true), + new HoursMinutes(18,0, true)), + new Timespan[0], + false, + new int[]{6}); + + adapter.setTimetables(timetables, Calendar.MONDAY); + + /* Expected parsed schedule: + * 0 - Mo, open + * 1 - Tu, closed + * 2 - We, open + * 3 - Th, closed + * 4 - Fr, open + * 5 - Sa-Su, closed + * */ + + List schedule = (List)mWeekScheduleField.get(adapter); + assertNotNull(schedule); + assertEquals(6, schedule.size()); + assertFalse(schedule.get(0).isClosed); // Mo - open + assertTrue(schedule.get(1).isClosed); // Tu - closed + assertFalse(schedule.get(2).isClosed); // We - open + assertTrue(schedule.get(3).isClosed); // Th - closed + assertFalse(schedule.get(4).isClosed); // Fr - open + assertTrue(schedule.get(5).isClosed); // Sa, Su - closed + assertEquals(schedule.get(5).startWeekDay, 7); + assertEquals(schedule.get(5).endWeekDay, 1); + } + + @Test + public void test_multiple_closed_day_sunday() throws IllegalAccessException + { + // opening_hours = "Mo 09:00-18:00; We 10:00-18:00; Fr 11:00-18:00" + Timetable[] timetables = new Timetable[3]; + timetables[0] = new Timetable(new Timespan(new HoursMinutes(9,0, true), + new HoursMinutes(18,0, true)), + new Timespan[0], + false, + new int[]{2}); + + timetables[1] = new Timetable(new Timespan(new HoursMinutes(10,0, true), + new HoursMinutes(18,0, true)), + new Timespan[0], + false, + new int[]{4}); + + timetables[2] = new Timetable(new Timespan(new HoursMinutes(11,0, true), + new HoursMinutes(18,0, true)), + new Timespan[0], + false, + new int[]{6}); + + adapter.setTimetables(timetables, Calendar.SUNDAY); + + /* Expected parsed schedule: + * 0 - Su, closed + * 1 - Mo, open + * 2 - Tu, closed + * 3 - We, open + * 4 - Th, closed + * 5 - Fr, open + * 6 - Sa, closed + * */ + + List schedule = (List)mWeekScheduleField.get(adapter); + assertNotNull(schedule); + assertEquals(7, schedule.size()); + assertTrue(schedule.get(0).isClosed); // Su - closed + assertFalse(schedule.get(1).isClosed); // Mo - open + assertTrue(schedule.get(2).isClosed); // Tu - closed + assertFalse(schedule.get(3).isClosed); // We - open + assertTrue(schedule.get(4).isClosed); // Th - closed + assertFalse(schedule.get(5).isClosed); // Fr - open + assertTrue(schedule.get(6).isClosed); // Sa - closed + assertEquals(schedule.get(6).startWeekDay, 7); + assertEquals(schedule.get(6).endWeekDay, 7); + } + + @Test + public void test_multiple_closed_days_2_monday() throws IllegalAccessException + { + // opening_hours = "Mo 09:00-18:00; We 10:00-18:00; Fr 11:00-18:00" + Timetable[] timetables = new Timetable[1]; + timetables[0] = new Timetable(new Timespan(new HoursMinutes(9,0, true), + new HoursMinutes(18,0, true)), + new Timespan[0], + false, + new int[]{2, 4, 6}); + + adapter.setTimetables(timetables, Calendar.MONDAY); + + /* Expected parsed schedule: + * 0 - Mo, open + * 1 - Tu, closed + * 2 - We, open + * 3 - Th, closed + * 4 - Fr, open + * 5 - Sa-Su, closed + * */ + + List schedule = (List)mWeekScheduleField.get(adapter); + assertNotNull(schedule); + assertEquals(6, schedule.size()); + assertFalse(schedule.get(0).isClosed); // Mo - open + assertTrue(schedule.get(1).isClosed); // Tu - closed + assertFalse(schedule.get(2).isClosed); // We - open + assertTrue(schedule.get(3).isClosed); // Th - closed + assertFalse(schedule.get(4).isClosed); // Fr - open + assertTrue(schedule.get(5).isClosed); // Sa, Su - closed + assertEquals(schedule.get(5).startWeekDay, 7); + assertEquals(schedule.get(5).endWeekDay, 1); + } + + @Test + public void test_multiple_closed_days_2_sunday() throws IllegalAccessException + { + // opening_hours = "Mo 09:00-18:00; We 10:00-18:00; Fr 11:00-18:00" + Timetable[] timetables = new Timetable[1]; + timetables[0] = new Timetable(new Timespan(new HoursMinutes(9,0, true), + new HoursMinutes(18,0, true)), + new Timespan[0], + false, + new int[]{2, 4, 6}); + + adapter.setTimetables(timetables, Calendar.SUNDAY); + + /* Expected parsed schedule: + * 0 - Su, closed + * 1 - Mo, open + * 2 - Tu, closed + * 3 - We, open + * 4 - Th, closed + * 5 - Fr, open + * 6 - Sa, closed + * */ + + List schedule = (List)mWeekScheduleField.get(adapter); + assertNotNull(schedule); + assertEquals(7, schedule.size()); + assertTrue(schedule.get(0).isClosed); // Su - closed + assertFalse(schedule.get(1).isClosed); // Mo - open + assertTrue(schedule.get(2).isClosed); // Tu - closed + assertFalse(schedule.get(3).isClosed); // We - open + assertTrue(schedule.get(4).isClosed); // Th - closed + assertFalse(schedule.get(5).isClosed); // Fr - open + assertTrue(schedule.get(6).isClosed); // Sa - closed + assertEquals(schedule.get(6).startWeekDay, 7); + assertEquals(schedule.get(6).endWeekDay, 7); + } + + @Test + public void test_open_weekend_sunday() throws IllegalAccessException + { + // opening_hours = "Sa-Su 11:00-24:00" + Timetable[] timetables = new Timetable[1]; + timetables[0] = new Timetable(new Timespan(new HoursMinutes(9,0, true), + new HoursMinutes(24,0, true)), + new Timespan[0], + false, + new int[]{1, 7}); + + adapter.setTimetables(timetables, Calendar.SUNDAY); + + /* Expected parsed schedule: + * 0 - Su, open + * 1 - Mo-Fr, closed + * 2 - Sa, open + * */ + + List schedule = (List)mWeekScheduleField.get(adapter); + assertNotNull(schedule); + assertEquals(3, schedule.size()); + assertFalse(schedule.get(0).isClosed); // Su - open + assertTrue(schedule.get(1).isClosed); // Mo-Fr - closed + assertFalse(schedule.get(2).isClosed); // Sa - open + assertEquals(schedule.get(0).startWeekDay, 1); + assertEquals(schedule.get(0).endWeekDay, 1); + assertEquals(schedule.get(1).startWeekDay, 2); + assertEquals(schedule.get(1).endWeekDay, 6); + assertEquals(schedule.get(2).startWeekDay, 7); + assertEquals(schedule.get(2).endWeekDay, 7); + } + + @Test + public void test_open_weekend_monday() throws IllegalAccessException + { + // opening_hours = "Sa-Su 11:00-24:00" + Timetable[] timetables = new Timetable[1]; + timetables[0] = new Timetable(new Timespan(new HoursMinutes(9,0, true), + new HoursMinutes(24,0, true)), + new Timespan[0], + false, + new int[]{1, 7}); + + adapter.setTimetables(timetables, Calendar.MONDAY); + + /* Expected parsed schedule: + * 0 - Mo-Fr, closed + * 1 - Sa-Su, open + * */ + + List schedule = (List)mWeekScheduleField.get(adapter); + assertNotNull(schedule); + assertEquals(2, schedule.size()); + assertTrue(schedule.get(0).isClosed); // Mo-Fr - closed + assertFalse(schedule.get(1).isClosed); // Sa-Su - open + assertEquals(schedule.get(1).startWeekDay, 7); + assertEquals(schedule.get(1).endWeekDay, 1); + } +} diff --git a/android/tests/resources/mockito-extensions/org.mockito.plugins.MockMaker b/android/tests/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 0000000000..ca6ee9cea8 --- /dev/null +++ b/android/tests/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline \ No newline at end of file