## Why
Drivers (sub-users) could not reliably reach the driver login from the
customer login page (/login) on mobile devices. The form contained an
internal driver button, but it sat below the QR code and passkey
buttons, making it easy to miss on small screens. The desktop sidebar
layout further reduced discoverability.
## What changed
- **src/views/auth/Login.vue** — Add a page-level .driver-entry block
below the customer login form, with a router-link to { name:
'subuserlogin' }. Distinct color (#1584BC) and a truck icon match the
existing LandingPage driver entry pattern. Full-width on mobile (<768px)
so it's impossible to miss.
- **i18n** — Add auth.driver_login_help to en, da, de, no, sv locale
files + global shared auth index. auth.driver_login_button already
existed in all locales.
- **tests/unit/login-driver-entry.spec.js** — 5 Vitest tests verifying
the driver entry block, router-link, button label, help text, and
placement (not in sidebar).
## CI
- Vitest unit tests pass (5/5 new tests, existing tests unaffected)
- ESLint clean
- Text encoding check clean
- i18n v2 source check + compile clean
Co-authored-by: Pleno Bugfix Bot <bugfix-bot@pleno.local>
135 lines
3.8 KiB
JavaScript
135 lines
3.8 KiB
JavaScript
// @vitest-environment jsdom
|
|
|
|
import { defineComponent, h, nextTick } from "vue";
|
|
import { mount } from "@vue/test-utils";
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const mocks = vi.hoisted(() => ({
|
|
hasStoredSessionToken: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("@/services/sessionStorage.js", () => ({
|
|
hasStoredSessionToken: mocks.hasStoredSessionToken,
|
|
}));
|
|
|
|
vi.mock("@/composables/useStoredSessionGuestRedirect.js", () => ({
|
|
useStoredSessionGuestRedirect: () => ({
|
|
hasPendingStoredSession: mocks.hasStoredSessionToken(),
|
|
}),
|
|
}));
|
|
|
|
vi.mock("@/components/forms/auth/LoginForm.vue", () => ({
|
|
default: defineComponent({
|
|
name: "MockLoginForm",
|
|
setup() {
|
|
return () => h("form", { "data-testid": "login-form" });
|
|
},
|
|
}),
|
|
}));
|
|
|
|
vi.mock("@/views/errors/ConnectivityIssue.vue", () => ({
|
|
default: defineComponent({
|
|
name: "MockConnectivityIssue",
|
|
setup(_, { slots }) {
|
|
return () => h("div", { "data-testid": "connectivity-wrapper" }, slots.default?.());
|
|
},
|
|
}),
|
|
}));
|
|
|
|
vi.mock("@/components/global/BrandedLoadingScreen.vue", () => ({
|
|
default: defineComponent({
|
|
name: "MockBrandedLoadingScreen",
|
|
props: {
|
|
testId: { type: String, default: "branded-loading-screen" },
|
|
},
|
|
setup(props) {
|
|
return () => h("div", { "data-testid": props.testId });
|
|
},
|
|
}),
|
|
}));
|
|
|
|
import Login from "@/views/auth/Login.vue";
|
|
|
|
const routerLinkStub = defineComponent({
|
|
name: "RouterLinkStub",
|
|
props: {
|
|
to: {
|
|
type: [String, Object],
|
|
default: "",
|
|
},
|
|
},
|
|
setup(_, { slots, attrs }) {
|
|
return () => h("a", attrs, slots.default?.());
|
|
},
|
|
});
|
|
|
|
const mountLogin = () =>
|
|
mount(Login, {
|
|
global: {
|
|
mocks: {
|
|
$t: (key) => key,
|
|
},
|
|
stubs: {
|
|
RouterLink: routerLinkStub,
|
|
},
|
|
},
|
|
});
|
|
|
|
describe("Login.vue — driver entry block (TRU-61)", () => {
|
|
beforeEach(() => {
|
|
mocks.hasStoredSessionToken.mockReset();
|
|
mocks.hasStoredSessionToken.mockReturnValue(false);
|
|
});
|
|
|
|
afterEach(() => {
|
|
document.body.innerHTML = "";
|
|
});
|
|
|
|
it("renders the page-level driver entry block on the customer login page", async () => {
|
|
const wrapper = mountLogin();
|
|
await nextTick();
|
|
|
|
const entry = wrapper.find('[data-testid="login-driver-entry"]');
|
|
expect(entry.exists()).toBe(true);
|
|
});
|
|
|
|
it("renders a router-link to the subuser login route inside the driver entry block", async () => {
|
|
const wrapper = mountLogin();
|
|
await nextTick();
|
|
|
|
const link = wrapper.find('[data-testid="login-driver-login-link"]');
|
|
expect(link.exists()).toBe(true);
|
|
});
|
|
|
|
it("renders the driver login button with the localized driver_login_button label", async () => {
|
|
const wrapper = mountLogin();
|
|
await nextTick();
|
|
|
|
const link = wrapper.find('[data-testid="login-driver-login-link"]');
|
|
expect(link.text()).toContain("auth.driver_login_button");
|
|
});
|
|
|
|
it("renders the driver login help text inside the entry block", async () => {
|
|
const wrapper = mountLogin();
|
|
await nextTick();
|
|
|
|
const entry = wrapper.find('[data-testid="login-driver-entry"]');
|
|
expect(entry.text()).toContain("auth.driver_login_help");
|
|
});
|
|
|
|
it("places the driver entry block in the main content area (not the sidebar)", async () => {
|
|
const wrapper = mountLogin();
|
|
await nextTick();
|
|
|
|
// The sidebar should NOT contain the driver entry — it must be in main
|
|
const html = wrapper.html();
|
|
const entryHtml = wrapper.find('[data-testid="login-driver-entry"]').html();
|
|
// Find the sidebar's outer container and ensure the entry is not inside it
|
|
const sidebarMatch = html.match(/<div class="sidebar">[\s\S]*?<\/div>\s*<div class="main">/);
|
|
if (sidebarMatch) {
|
|
expect(sidebarMatch[0]).not.toContain("login-driver-entry");
|
|
}
|
|
expect(entryHtml).toBeTruthy();
|
|
});
|
|
});
|