diff --git a/e2e/profile.spec.ts b/e2e/profile.spec.ts index 93d9248c..30231262 100644 --- a/e2e/profile.spec.ts +++ b/e2e/profile.spec.ts @@ -230,6 +230,37 @@ test('test that theme can be changed to dark and light', async ({ page }) => { await expect(page.getByText('System default:')).toBeVisible(); }); +// ============================================= +// Group similar time entries +// ============================================= + +test('test that group similar time entries setting can be toggled', async ({ page }) => { + await goToProfilePage(page); + + // Get the checkbox + const checkbox = page.getByLabel('Group similar time entries'); + + // Get initial value and verify it is checked (default is true) + const initialValue = await checkbox.isChecked(); + await expect(checkbox).toBeChecked(); + + // Toggle the checkbox + await checkbox.click(); + + // Reload + await page.reload(); + + // Verify the value is toggled + const afterValue = await page.getByLabel('Group similar time entries').isChecked(); + expect(afterValue).toBe(!initialValue); + + // Verify localStorage persists the setting + const storedValue = await page.evaluate(() => + localStorage.getItem('group-similar-time-entries') + ); + expect(storedValue).toBe(String(!initialValue)); +}); + // ============================================= // Two Factor Authentication Tests // ============================================= diff --git a/e2e/time.spec.ts b/e2e/time.spec.ts index 9c311b03..dfd4ba4e 100644 --- a/e2e/time.spec.ts +++ b/e2e/time.spec.ts @@ -39,6 +39,10 @@ function getMonthFromTimestamp(timestamp: string): number { return new Date(timestamp).getUTCMonth() + 1; } +async function goToProfilePage(page: Page) { + await page.goto(PLAYWRIGHT_BASE_URL + '/user/profile'); +} + async function goToTimeOverview(page: Page) { await page.goto(PLAYWRIGHT_BASE_URL + '/time'); } @@ -67,6 +71,14 @@ async function createEmptyTimeEntry(page: Page) { ]); } +async function setTimeEntriesGrouping(page: Page, enabled: boolean) { + await goToProfilePage(page); + const checkbox = page.getByLabel('Group similar time entries'); + const isChecked = await checkbox.isChecked(); + if (isChecked !== enabled) await checkbox.click(); + await goToTimeOverview(page); +} + test('test that starting and stopping an empty time entry shows a new time entry in the overview', async ({ page, }) => { @@ -333,6 +345,30 @@ test.skip('test that load more works when the end of page is reached', async ({ await expect(page.locator('body')).toHaveText(/All time entries are loaded!/); }); +test('test that Group similar time entries option is affected', async ({ page }) => { + // Enable grouping + await setTimeEntriesGrouping(page, true); + + // Create 2 similar time entries + await createEmptyTimeEntry(page); + await page.waitForSelector('[data-testid="time_entry_row"]', { timeout: 1000 }); + await createEmptyTimeEntry(page); + + // Verify similar time entries are grouped + await expect(page.getByTestId('grouped_items_count_button').first()).toBeVisible({ + timeout: 1000, + }); + + // Disable grouping + await setTimeEntriesGrouping(page, false); + + // Verify similar time entries are not grouped + await expect(page.locator('[data-testid="time_entry_row"]')).toHaveCount(2, { timeout: 1000 }); + await expect(page.locator('[data-testid="grouped_items_count_button"]')).toHaveCount(0, { + timeout: 1000, + }); +}); + // TODO: Test that updating the time entry start / end times works while it is running // TODO: Test for project update diff --git a/resources/js/Pages/Profile/Partials/ThemeForm.vue b/resources/js/Pages/Profile/Partials/ThemeForm.vue index 69de1786..022a1d70 100644 --- a/resources/js/Pages/Profile/Partials/ThemeForm.vue +++ b/resources/js/Pages/Profile/Partials/ThemeForm.vue @@ -2,8 +2,10 @@ import FormSection from '@/Components/FormSection.vue'; import { Field, FieldLabel, FieldDescription } from '@/packages/ui/src/field'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/packages/ui/src'; +import { Checkbox } from '@/packages/ui/src'; import { usePreferredColorScheme } from '@vueuse/core'; import { themeSetting } from '@/utils/theme'; +import { groupSimilarTimeEntriesSetting } from '@/utils/timeEntryGrouping'; const preferredColor = usePreferredColorScheme(); @@ -15,6 +17,7 @@ const preferredColor = usePreferredColorScheme(); diff --git a/resources/js/Pages/Time.vue b/resources/js/Pages/Time.vue index 7d772682..ebc210e9 100644 --- a/resources/js/Pages/Time.vue +++ b/resources/js/Pages/Time.vue @@ -16,6 +16,7 @@ import { useElementVisibility } from '@vueuse/core'; import { ClockIcon } from '@heroicons/vue/20/solid'; import LoadingSpinner from '@/packages/ui/src/LoadingSpinner.vue'; import { useCurrentTimeEntryStore } from '@/utils/useCurrentTimeEntry'; +import { groupSimilarTimeEntriesSetting } from '@/utils/timeEntryGrouping'; import { useTasksQuery } from '@/utils/useTasksQuery'; import { useProjectsQuery } from '@/utils/useProjectsQuery'; import TimeEntryGroupedTable from '@/packages/ui/src/TimeEntry/TimeEntryGroupedTable.vue'; @@ -151,6 +152,7 @@ function deleteSelected() { :tasks="tasks" :currency="getOrganizationCurrencyString()" :time-entries="timeEntries" + :group-similar-time-entries="groupSimilarTimeEntriesSetting" :tags="tags">
diff --git a/resources/js/packages/ui/src/GroupedItemsCountButton.vue b/resources/js/packages/ui/src/GroupedItemsCountButton.vue index 23a1d965..c9a8a1f1 100644 --- a/resources/js/packages/ui/src/GroupedItemsCountButton.vue +++ b/resources/js/packages/ui/src/GroupedItemsCountButton.vue @@ -6,10 +6,15 @@ const props = withDefaults( defineProps<{ expanded?: boolean; size?: string; + /** + * Test ID used for Playwright/E2E tests. + */ + testId?: string; }>(), { expanded: false, size: 'w-7 h-7', + testId: 'grouped_items_count_button', } ); @@ -23,6 +28,7 @@ const expandedStatusClasses = computed(() => {