diff --git a/src/components/displays/pagination/PaginationOtherFiltersDropdown.vue b/src/components/displays/pagination/PaginationOtherFiltersDropdown.vue index c90d1726..18230cfc 100644 --- a/src/components/displays/pagination/PaginationOtherFiltersDropdown.vue +++ b/src/components/displays/pagination/PaginationOtherFiltersDropdown.vue @@ -134,7 +134,7 @@ onBeforeUnmount(() => { padding: 0 0.3rem; pointer-events: none; position: absolute; - right: -0.35rem; + right: -0.55rem; top: -0.25rem; z-index: 1; } diff --git a/src/components/displays/pagination/models/SuperUserDashboard/SubusersPagination.vue b/src/components/displays/pagination/models/SuperUserDashboard/SubusersPagination.vue index ec185e23..d3375b8a 100644 --- a/src/components/displays/pagination/models/SuperUserDashboard/SubusersPagination.vue +++ b/src/components/displays/pagination/models/SuperUserDashboard/SubusersPagination.vue @@ -27,6 +27,7 @@ const props = defineProps({ default: null, }, }); +const emit = defineEmits(["mutated"]); const paginatedList = usePaginatedList(); provide(PaginatedListKey, paginatedList); @@ -52,6 +53,10 @@ if (props.hideSearch) { if (props.autoLoad) { loadList(); } + +const onTableMutated = () => { + emit("mutated"); +}; diff --git a/src/components/displays/superuser/tables/SubusersTable.vue b/src/components/displays/superuser/tables/SubusersTable.vue index 772774d4..16d3affd 100644 --- a/src/components/displays/superuser/tables/SubusersTable.vue +++ b/src/components/displays/superuser/tables/SubusersTable.vue @@ -1,5 +1,6 @@ - + diff --git a/src/i18n/generated/da-v2.json b/src/i18n/generated/da-v2.json index 5e1e8601..7e504deb 100644 --- a/src/i18n/generated/da-v2.json +++ b/src/i18n/generated/da-v2.json @@ -4679,6 +4679,7 @@ }, "pagination": { "archive": "Arkiv", + "clear_all": "Ryd alle", "ascending": "Stigende (Ældste @:{'words.generated.først'})", "booking_status": "Bogføringsstatus", "bookings_overview": "@:{'templates.generated.compat.objects.bookings.description'}", diff --git a/src/i18n/generated/no-v2.json b/src/i18n/generated/no-v2.json index 6757c021..c525172e 100644 --- a/src/i18n/generated/no-v2.json +++ b/src/i18n/generated/no-v2.json @@ -4793,6 +4793,7 @@ }, "pagination": { "archive": "Arkiv", + "clear_all": "Tøm alle", "ascending": "Stigende (eldst @:{'words.generated.først'})", "booking_status": "Bestillingsstatus", "bookings_overview": "Bestillingsoversikt", diff --git a/src/i18n/source/da/phrases/compat/pagination/index.json b/src/i18n/source/da/phrases/compat/pagination/index.json index 502ed00a..08aaf845 100644 --- a/src/i18n/source/da/phrases/compat/pagination/index.json +++ b/src/i18n/source/da/phrases/compat/pagination/index.json @@ -2,6 +2,7 @@ "compat": { "pagination": { "archive": "Arkiv", + "clear_all": "Ryd alle", "ascending": "Stigende (Ældste @:{'terms.glossary.først'})", "booking_status": "Bogføringsstatus", "bookings_overview": "@:{'phrases.compat.objects.bookings.description'}", diff --git a/src/i18n/source/no/phrases/compat/pagination/index.json b/src/i18n/source/no/phrases/compat/pagination/index.json index a7718c28..eddb18c7 100644 --- a/src/i18n/source/no/phrases/compat/pagination/index.json +++ b/src/i18n/source/no/phrases/compat/pagination/index.json @@ -2,6 +2,7 @@ "compat": { "pagination": { "archive": "Arkiv", + "clear_all": "Tøm alle", "ascending": "Stigende (eldst @:{'terms.glossary.først'})", "booking_status": "Bestillingsstatus", "bookings_overview": "Bestillingsoversikt", diff --git a/src/views/dashboards/superUserDashboard/user/UserSubusers.vue b/src/views/dashboards/superUserDashboard/user/UserSubusers.vue index 58dda9ea..4a9592a0 100644 --- a/src/views/dashboards/superUserDashboard/user/UserSubusers.vue +++ b/src/views/dashboards/superUserDashboard/user/UserSubusers.vue @@ -171,6 +171,7 @@ watch( :endpoint="endpoint" :user-scoped-user-id="routeUserId" auto-load="true" + @mutated="loadSummary" /> diff --git a/tests/e2e/subuser-management.spec.ts b/tests/e2e/subuser-management.spec.ts index 712b3e28..7b1abcad 100644 --- a/tests/e2e/subuser-management.spec.ts +++ b/tests/e2e/subuser-management.spec.ts @@ -886,8 +886,10 @@ test("customer user with own-subuser permissions can invite and manage grant acc await page.getByTestId("subuser-dognvask-tooltip-3").hover(); await expect(page.locator(".tooltip-content:visible")).toContainText("Kundeadgangen er deaktiveret"); await page.mouse.move(0, 0); - await page.getByTestId("subuser-remove-2").hover(); - await expect(page.locator(".tooltip-content", { hasText: "Deaktiverer chaufførens kundeadgang" })).toBeVisible(); + await page.locator(".b-tooltip", { has: page.getByTestId("subuser-remove-2") }).hover(); + await expect( + page.locator(".tooltip-content:visible", { hasText: "Deaktiverer chaufførens kundeadgang" }).first() + ).toContainText("Deaktiverer chaufførens kundeadgang"); await page.getByRole("button", { name: /Invit.*chauff/i }).click(); await page.fill("#subuser-form-name", "Invited Driver"); @@ -901,16 +903,17 @@ test("customer user with own-subuser permissions can invite and manage grant acc await expect(page.getByTestId("subuser-phone-100")).toHaveText("+45 44444444"); await expect(page.getByTestId("subuser-plate-100")).toHaveText("-"); - await page.getByTestId("subuser-plate-edit-100").click(); - await page.selectOption("#subuser-assigned-vehicle", "104"); - await page.getByRole("button", { name: "Gem" }).click(); - await expect(page.getByTestId("subuser-plate-100")).toHaveText("NY44444"); - let actions = await openSubuserActions(page, 100); await expect(actions.getByTestId("subuser-section-account-100")).toHaveCount(0); await expect(actions.getByTestId("subuser-section-vehicle-100")).toBeVisible(); await expect(actions.getByTestId("subuser-section-grant-100")).toBeVisible(); await expect(actions.getByTestId("subuser-section-details-100")).toBeVisible(); + await actions.getByTestId("subuser-assign-vehicle-100").click(); + await page.selectOption("#subuser-assigned-vehicle", "104"); + await page.getByRole("button", { name: "Gem" }).click(); + await expect(page.getByTestId("subuser-plate-100")).toHaveText("NY44444"); + + actions = await openSubuserActions(page, 100); await actions.getByTestId("subuser-permissions-100").click(); await expect(page.getByText("Adgangsprofil for Invited Driver")).toBeVisible(); await expect(page.getByTestId("permission-template-driver")).toBeVisible(); @@ -985,14 +988,19 @@ test("superusers can list and invite chauffeurs across customers", async ({ page await expect(actions.getByTestId("subuser-section-details-51-510")).toBeVisible(); await expect(actions).toContainText("12345678 - Nordic Transport"); await expect(actions.getByTestId("subuser-resend-51-510")).toBeVisible(); + await expect(page.getByTestId("subuser-name-edit-51-510")).toHaveCount(0); + await expect(page.getByTestId("subuser-phone-edit-51-510")).toHaveCount(0); + await expect(page.getByTestId("subuser-plate-edit-51-510")).toHaveCount(0); + await expect(page.getByTestId("subuser-dognvask-51-511")).toBeVisible(); - await page.getByTestId("subuser-name-edit-51-510").click(); + await actions.getByTestId("subuser-edit-name-51-510").click(); await page.fill(".swal2-input", "Renamed Super Driver"); await page.getByRole("button", { name: "Gem" }).click(); await expect(page.getByTestId("subuser-name-51-510")).toContainText("Renamed Super Driver"); expect(profilePayloads).toContainEqual({ id: 51, payload: { name: "Renamed Super Driver" } }); - await page.getByTestId("subuser-phone-edit-51-510").click(); + actions = await openSubuserActions(page, "51-510"); + await actions.getByTestId("subuser-edit-contact-51-510").click(); await page.fill("#subuser-admin-email", "renamed@example.com"); await page.fill("#subuser-admin-phone-country-code", "46"); await page.fill("#subuser-admin-phone", "12312312"); @@ -1007,7 +1015,8 @@ test("superusers can list and invite chauffeurs across customers", async ({ page }, }); - await page.getByTestId("subuser-plate-edit-51-510").click(); + actions = await openSubuserActions(page, "51-510"); + await actions.getByTestId("subuser-assign-vehicle-51-510").click(); await page.selectOption("#subuser-assigned-vehicle", "5102"); await page.getByRole("button", { name: "Gem" }).click(); await expect(page.getByTestId("subuser-plate-51-510")).toHaveText("NT24680"); diff --git a/tests/e2e/superuser-orders-date-filters.spec.ts b/tests/e2e/superuser-orders-date-filters.spec.ts index 832627f8..b294df51 100644 --- a/tests/e2e/superuser-orders-date-filters.spec.ts +++ b/tests/e2e/superuser-orders-date-filters.spec.ts @@ -88,6 +88,7 @@ test.describe("superuser order date filters", () => { await expect(otherFiltersButton).toBeVisible(); await expect(otherFiltersButton).not.toHaveClass(/is-small/); const otherFiltersCount = page.getByTestId("invoice-orders-other-filters-count"); + const clearOtherFiltersButton = page.getByTestId("invoice-orders-other-filters-clear"); await expect(otherFiltersCount).toHaveText("3"); await expect(anytimeDateShortcut).toBeVisible(); await expect(todayDateShortcut).toBeVisible(); @@ -107,8 +108,8 @@ test.describe("superuser order date filters", () => { const shortcutCenterY = shortcutBox.y + shortcutBox.height / 2; const otherToAnytimeGap = shortcutBox.x - (buttonBox.x + buttonBox.width); const dateShortcutGap = todayShortcutBox.x - (shortcutBox.x + shortcutBox.width); - const doesCountProtrudeFromTopRight = - countBox.y < buttonBox.y && countBox.x + countBox.width > buttonBox.x + buttonBox.width; + const countRightProtrusion = countBox.x + countBox.width - (buttonBox.x + buttonBox.width); + const doesCountProtrudeFromTopRight = countBox.y < buttonBox.y && countRightProtrusion >= 8; const hasCountTopClearance = countBox.y >= shortcutActionsBox.y + 2; return ( @@ -120,35 +121,29 @@ test.describe("superuser order date filters", () => { ); }) .toBe(true); - await expect(otherFiltersMenu).toBeHidden(); - - await otherFiltersButton.click(); - await expect(otherFiltersMenu).toBeVisible(); - for (const label of [ - "Rækkefølge", - "Bogføringsstatus", - "Betalingsprocessor", - "Fuldført", - "Faktura valg", - "Specialaftale", - "Vaskeabonnement", - ]) { - await expect(otherFiltersLabel(label)).toBeVisible(); + const verifiedButtonBox = await otherFiltersButton.boundingBox(); + const verifiedCountBox = await otherFiltersCount.boundingBox(); + const verifiedShortcutBox = await anytimeDateShortcut.boundingBox(); + if (!verifiedButtonBox || !verifiedCountBox || !verifiedShortcutBox) { + throw new Error("Unable to capture verified other filters badge screenshot"); } - - await page.getByTestId("invoice-orders-other-filter-processor").selectOption("2"); - await expect(otherFiltersCount).toHaveText("4"); - await expect.poll(() => orderRequests.some((request) => request.filters.includes("processor:2"))).toBeTruthy(); - - await page.keyboard.press("Escape"); + const verifiedTop = Math.min(verifiedButtonBox.y, verifiedCountBox.y); + const verifiedBottom = Math.max( + verifiedButtonBox.y + verifiedButtonBox.height, + verifiedCountBox.y + verifiedCountBox.height, + verifiedShortcutBox.y + verifiedShortcutBox.height + ); + await page.screenshot({ + path: "output/playwright/manual/invoice-orders-other-filters-badge-right-protrusion.png", + clip: { + x: Math.max(0, verifiedButtonBox.x - 28), + y: Math.max(0, verifiedTop - 24), + width: Math.ceil(verifiedShortcutBox.x + verifiedShortcutBox.width - verifiedButtonBox.x + 56), + height: Math.ceil(verifiedBottom - verifiedTop + 48), + }, + }); await expect(otherFiltersMenu).toBeHidden(); - await expect - .poll(() => - orderRequests.some((request) => request.limit === 1000 && !request.filters.includes("created_at-date_")) - ) - .toBeTruthy(); - const fromField = page.getByTestId("invoice-orders-date-from"); const fromInput = fromField.locator("input").first(); const toInput = page.getByTestId("invoice-orders-date-to").locator("input").first(); @@ -177,6 +172,80 @@ test.describe("superuser order date filters", () => { .poll(() => orderRequests.some((request) => request.filters.includes("created_at-date_from:2026-07-02"))) .toBeTruthy(); + await otherFiltersButton.click(); + await expect(otherFiltersMenu).toBeVisible(); + for (const label of [ + "Rækkefølge", + "Bogføringsstatus", + "Betalingsprocessor", + "Fuldført", + "Faktura valg", + "Specialaftale", + "Vaskeabonnement", + ]) { + await expect(otherFiltersLabel(label)).toBeVisible(); + } + await expect(clearOtherFiltersButton).toBeVisible(); + await expect(clearOtherFiltersButton).toHaveText("Ryd alle"); + await expect + .poll(async () => { + const menuBox = await otherFiltersMenu.boundingBox(); + const clearBox = await clearOtherFiltersButton.boundingBox(); + const lastSelectBox = await page.getByTestId("invoice-orders-other-filter-wash-subscription").boundingBox(); + + if (!menuBox || !clearBox || !lastSelectBox) { + return false; + } + + return ( + clearBox.y > lastSelectBox.y + lastSelectBox.height && + clearBox.y + clearBox.height <= menuBox.y + menuBox.height + ); + }) + .toBe(true); + + await page.getByTestId("invoice-orders-other-filter-processor").selectOption("2"); + await expect(otherFiltersCount).toHaveText("4"); + await expect.poll(() => orderRequests.some((request) => request.filters.includes("processor:2"))).toBeTruthy(); + + const requestCountBeforeClear = orderRequests.length; + await clearOtherFiltersButton.click(); + + await expect.poll(() => orderRequests.length > requestCountBeforeClear).toBeTruthy(); + await expect(page.getByTestId("invoice-orders-other-filters-count")).toHaveCount(0); + await expect(page.getByTestId("invoice-orders-other-filter-order-direction")).toHaveValue("desc"); + for (const filterTestId of [ + "invoice-orders-other-filter-booked-invoice-id", + "invoice-orders-other-filter-processor", + "invoice-orders-other-filter-completed-at", + "invoice-orders-other-filter-invoice-selection", + "invoice-orders-other-filter-special-agreement", + "invoice-orders-other-filter-wash-subscription", + ]) { + await expect(page.getByTestId(filterTestId)).toHaveValue("*"); + } + await expect(fromInput).toHaveValue("02.07.2026"); + await expect(toInput).toHaveValue(""); + await expect + .poll(() => { + const latestTableRequest = [...orderRequests].reverse().find((request) => request.limit !== 1000); + + return Boolean( + latestTableRequest?.filters.includes("created_at-date_from:2026-07-02") && + !latestTableRequest.filters.includes("processor:2") + ); + }) + .toBeTruthy(); + + await page.keyboard.press("Escape"); + await expect(otherFiltersMenu).toBeHidden(); + + await expect + .poll(() => + orderRequests.some((request) => request.limit === 1000 && !request.filters.includes("created_at-date_")) + ) + .toBeTruthy(); + await fromField.locator(".icon.is-right").click(); await expect(fromInput).toHaveValue(""); diff --git a/tests/e2e/superuser-security.spec.ts b/tests/e2e/superuser-security.spec.ts index e2fb59bb..d0a518d3 100644 --- a/tests/e2e/superuser-security.spec.ts +++ b/tests/e2e/superuser-security.spec.ts @@ -210,6 +210,9 @@ test.describe("Superuser system security", () => { await page.goto("/superuser/system/security/overview"); await expect(page.getByTestId("superuser-security-page")).toBeVisible(); + const desktopNavigation = page.getByTestId("desktop-buefy-navigation"); + await expect(desktopNavigation.getByText("System", { exact: true })).toBeVisible(); + await expect(desktopNavigation.getByText("Security", { exact: true })).toBeVisible(); await expect(page.getByText("Security overview")).toBeVisible(); await expect(page.getByRole("link", { name: "Open incidents" })).toBeVisible(); await expect(page.getByText("Block firewall rule matched: ip 203.0.113.9")).toBeVisible(); diff --git a/tests/unit/date-period-selector.spec.js b/tests/unit/date-period-selector.spec.js index 2b2e7afd..89fe4ac6 100644 --- a/tests/unit/date-period-selector.spec.js +++ b/tests/unit/date-period-selector.spec.js @@ -433,6 +433,7 @@ describe("DatePeriodSelector month warning", () => { showUpdateButton: false, showStartDate: false, showEndDate: false, + showToLabel: false, showSelectionValidity: false, showMonthSelector: false, showYearSelector: false, @@ -450,6 +451,8 @@ describe("DatePeriodSelector month warning", () => { expect(anytimeButton.classes()).toContain("is-info"); expect(todayButton.classes()).not.toContain("is-info"); + expect(wrapper.find(".level").exists()).toBe(false); + expect(wrapper.get("[data-testid='date-period-shortcut-anytime']").exists()).toBe(true); }); it("hides empty range controls unless the selector is optional", () => { diff --git a/tests/unit/navigation-menu-items-admin-contract.spec.js b/tests/unit/navigation-menu-items-admin-contract.spec.js index 39992a18..4d61c272 100644 --- a/tests/unit/navigation-menu-items-admin-contract.spec.js +++ b/tests/unit/navigation-menu-items-admin-contract.spec.js @@ -67,7 +67,7 @@ describe("NavigationMenuItemsAdmin contract", () => { expect(superuserSource).not.toContain('to: "/superuser/system/replication"'); expect(superuserLeftMenuSource).toContain('t("security.nav", "Security")'); - expect(superuserLeftMenuSource).toContain("value: '/system/security/overview'"); + expect(superuserLeftMenuSource).toContain('value: "/system/security/overview"'); expect(superuserLeftMenuSource).toContain('t("cron.system_nav", "System")'); }); });