## Summary - Centralize subuser permission capability labels so the grant editor, table header, and action settings wheel use the same self-service text. - Add the self-service access control to each grant section in the subuser action wheel. - Add focused unit coverage for the shared label helper and subuser action wheel payload. ## Verification - `npm run test:unit:single -- tests/unit/subuser-management-labels.spec.js tests/unit/subuser-grant-permission-nodes.spec.js` - `npx eslint src/components/displays/selectors/SubuserGrantPermissionNodes.vue src/components/displays/superuser/tables/SubusersTable.vue src/components/session/subuser/subuserPermissionLabels.js tests/unit/subuser-management-labels.spec.js --quiet` - `npm run build` ## Visual change previews ### View: Customer subuser management **Description:** Shows the customer-facing chauffeur table and settings wheel self-service label aligned to the same wording across table header, row control, and wheel item. #### Mobile (390x844) **Before:**  **After:**  #### Tablet (768x1024) **Before:**  **After:**  #### Desktop (1440x900) **Before:**  **After:**  --------- Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk>
168 lines
5.2 KiB
JavaScript
168 lines
5.2 KiB
JavaScript
// @vitest-environment jsdom
|
|
import { mount } from "@vue/test-utils";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import { createI18n } from "vue-i18n";
|
|
|
|
const translateMock = vi.hoisted(() => {
|
|
const translations = {
|
|
"common.active": "Active",
|
|
"common.inactive": "Inactive",
|
|
"common.disabled": "Disabled",
|
|
"templates.generated.compat.common.none": "None",
|
|
"templates.generated.compat.superuser.driver_contact.missing": "Missing",
|
|
"templates.generated.compat.superuser.driver_contact.verified": "Verified",
|
|
"templates.generated.compat.superuser.driver_contact.unverified": "Unverified",
|
|
"templates.generated.compat.superuser.driver_wheel.account_section": "Account",
|
|
"templates.generated.compat.superuser.driver_wheel.contact_section": "Contact",
|
|
"templates.generated.compat.superuser.driver_wheel.access_section": "Access",
|
|
"templates.generated.compat.superuser.driver_wheel.details_section": "Details",
|
|
"templates.generated.compat.superuser.driver_wheel.identity_subsection": "Identity",
|
|
"templates.generated.compat.superuser.driver_wheel.system_subsection": "System",
|
|
"superuser.driver_access.capabilities.start_selfserve": "Start self-service",
|
|
"superuser.driver_access.templates.driver.label": "Driver",
|
|
"superuser.driver_access.summary.no_permissions": "No permissions",
|
|
"admin.pos.settings_wheel.shortcuts_section": "Shortcuts",
|
|
};
|
|
|
|
return (key, values = undefined) => {
|
|
const value = translations[key] || key;
|
|
if (!values) {
|
|
return value;
|
|
}
|
|
|
|
return Object.entries(values).reduce(
|
|
(text, [name, replacement]) => text.replace(`{${name}}`, String(replacement)),
|
|
value
|
|
);
|
|
};
|
|
});
|
|
|
|
vi.mock("@/i18n", () => ({
|
|
default: {
|
|
global: {
|
|
t: translateMock,
|
|
},
|
|
},
|
|
}));
|
|
|
|
vi.mock("@/components/session/token/SessionUser.vue", () => ({
|
|
SessionUser: {
|
|
canAccessSuperUser: vi.fn(() => false),
|
|
canManageSubusers: vi.fn(() => true),
|
|
objects: {
|
|
subusers: {
|
|
functions: {
|
|
permissionSummary: vi.fn(() => "Driver"),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}));
|
|
|
|
const wheelSections = [];
|
|
vi.mock("@/components/displays/buttons/ActionSettingsWheelButton.vue", () => ({
|
|
default: {
|
|
props: ["menuSections"],
|
|
setup(props) {
|
|
wheelSections.push(props.menuSections);
|
|
return () => null;
|
|
},
|
|
},
|
|
}));
|
|
|
|
import SubusersTable from "@/components/displays/superuser/tables/SubusersTable.vue";
|
|
import {
|
|
permissionCapabilityLabel,
|
|
selfServeAccessLabel,
|
|
} from "@/components/session/subuser/subuserPermissionLabels.js";
|
|
import { readFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
|
|
const mountTable = () =>
|
|
mount(SubusersTable, {
|
|
props: {
|
|
objects: [
|
|
{
|
|
id: 7,
|
|
username: "driver",
|
|
name: "Demo Driver",
|
|
phone_country_code: 45,
|
|
phone: 12345678,
|
|
created_at: "2026-07-27 12:00:00",
|
|
updated_at: "2026-07-27 12:00:00",
|
|
grant_id: 77,
|
|
grant_enabled: true,
|
|
grant_permissions: ["SELFSERVE_LIST", "SELFSERVE_ADD"],
|
|
permission_template_key: "driver",
|
|
access_state: "active",
|
|
},
|
|
],
|
|
},
|
|
global: {
|
|
plugins: [
|
|
createI18n({
|
|
legacy: false,
|
|
locale: "en",
|
|
missingWarn: false,
|
|
fallbackWarn: false,
|
|
messages: { en: {} },
|
|
}),
|
|
],
|
|
stubs: {
|
|
"b-switch": {
|
|
props: ["modelValue"],
|
|
template: '<input type="checkbox" :checked="modelValue" />',
|
|
},
|
|
"b-tooltip": {
|
|
template: "<span><slot /></span>",
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
const flattenItems = (sections) =>
|
|
sections.flatMap((section) =>
|
|
(section.items || []).flatMap((item) => (item.items ? [item, ...flattenItems([item])] : [item]))
|
|
);
|
|
|
|
describe("subuser management labels", () => {
|
|
it("uses one self-service capability label for helpers", () => {
|
|
expect(permissionCapabilityLabel("selfserve_add")).toBe("Start self-service");
|
|
expect(selfServeAccessLabel()).toBe("Start self-service");
|
|
});
|
|
|
|
it("uses the same self-service label in the table header and settings wheel item", () => {
|
|
wheelSections.length = 0;
|
|
const wrapper = mountTable();
|
|
|
|
expect(wrapper.find("thead").text()).toContain("Start self-service");
|
|
|
|
const wheelItems = flattenItems(wheelSections.at(-1) || []);
|
|
expect(wheelItems).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
key: "subuser-self-serve",
|
|
label: "Start self-service",
|
|
type: "select",
|
|
}),
|
|
])
|
|
);
|
|
});
|
|
|
|
it("restores the settings-wheel selection when the permission update fails", () => {
|
|
const tableSource = readFileSync(
|
|
join(process.cwd(), "src/components/displays/superuser/tables/SubusersTable.vue"),
|
|
"utf8"
|
|
);
|
|
const selectSource = readFileSync(
|
|
join(process.cwd(), "src/components/displays/buttons/ActionSettingsWheelSelectItem.vue"),
|
|
"utf8"
|
|
);
|
|
|
|
expect(tableSource).toContain("if (rejectOnFailure)");
|
|
expect(tableSource).toContain("onToggleDognvaskFromSettingsWheel");
|
|
expect(selectSource).toContain("localValue.value = normalizeSelectValue(props.value)");
|
|
expect(selectSource).toContain("} catch {");
|
|
});
|
|
});
|