## Summary
Customer experience series report (TRU-130, Slack
`#alle_tw_alle_afdelinger` ts `1786629381.499969`) noted that the wax
product ("Højglans - Voksforsegling pr. enhed", product_id 26) tested
poorly in the field and the operations team is trialing a different wax
supplier.
While the API payload drives the actual daily-report product title, the
`FALLBACK_PRODUCT_TITLES` map in the Vue display component and the
matching e2e expectations still hardcoded the failed brand name. After
the supplier swap the tile would have shown a stale brand even when the
API reports the new product, and the test expectations would have failed
on legitimate label updates.
## Changes
-
`src/views/dashboards/departmentDashboard/modules/daily-report/displays/DepartmentDashboardDailyReportProductSales.vue`
- Drop the `Højglans - ` prefix from the product 26 fallback title so
the tile stays accurate while the wax supplier is iterated on.
- Add a TRU-130 reference comment explaining the choice.
- `src/components/displays/department/pos/displays/Piktogrammer.vue`
- Update the product 26 comment to be brand-agnostic.
- `tests/e2e/admin-daily-report.spec.ts`
- Update the two `daily-report-tile-hoejglans` expectations to match the
new fallback title.
- `tests/unit/department-daily-report-product-sales.behavior.spec.js`
- Add a regression test that locks the brand-agnostic fallback for
product 26.
## Verification
- `npx vitest run
tests/unit/department-daily-report-product-sales.behavior.spec.js` → 5
tests passed (including the new TRU-130 case).
## Linked Linear issue
TRU-130 — https://linear.app/truck-wash-aps/issue/TRU-130
Co-authored-by: Pleno Bugfix Bot <bugfix-bot@pleno.local>
196 lines
6.1 KiB
JavaScript
196 lines
6.1 KiB
JavaScript
// @vitest-environment jsdom
|
|
import { mount } from "@vue/test-utils";
|
|
import { nextTick } from "vue";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const dailyReportProductsState = vi.hoisted(() => ({ value: {} }));
|
|
const selectedDepartmentIdsState = vi.hoisted(() => ({ value: [] }));
|
|
const refreshOverviewMock = vi.hoisted(() => vi.fn());
|
|
const hasPermissionMock = vi.hoisted(() => vi.fn());
|
|
const setProductTargetMock = vi.hoisted(() => vi.fn());
|
|
|
|
vi.mock("@/views/dashboards/departmentDashboard/modules/daily-report/DepartmentDailyReportObject.vue", () => ({
|
|
daily_report_products: dailyReportProductsState,
|
|
selected_department_ids: selectedDepartmentIdsState,
|
|
refreshOverview: refreshOverviewMock,
|
|
}));
|
|
|
|
vi.mock("@/components/session/token/SessionUser.vue", () => ({
|
|
SessionUser: {
|
|
hasPermission: hasPermissionMock,
|
|
objects: {
|
|
department_daily_reports: {
|
|
functions: {
|
|
setProductTarget: setProductTargetMock,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}));
|
|
|
|
vi.mock("vue-i18n", async (importOriginal) => {
|
|
const actual = await importOriginal();
|
|
|
|
return {
|
|
...actual,
|
|
useI18n: () => ({
|
|
t: (value) => value,
|
|
}),
|
|
};
|
|
});
|
|
|
|
import DepartmentDashboardDailyReportProductSales from "@/views/dashboards/departmentDashboard/modules/daily-report/displays/DepartmentDashboardDailyReportProductSales.vue";
|
|
|
|
describe("DepartmentDashboardDailyReportProductSales behavior", () => {
|
|
beforeEach(() => {
|
|
dailyReportProductsState.value = {};
|
|
selectedDepartmentIdsState.value = [];
|
|
refreshOverviewMock.mockReset();
|
|
hasPermissionMock.mockReset();
|
|
hasPermissionMock.mockReturnValue(false);
|
|
setProductTargetMock.mockReset();
|
|
setProductTargetMock.mockResolvedValue({ data: { data: { target_percentage: null } } });
|
|
});
|
|
|
|
it("renders the API title when the overview payload includes one", () => {
|
|
dailyReportProductsState.value = {
|
|
24: {
|
|
title: "Custom API Title",
|
|
value: 3,
|
|
out_of: 8,
|
|
state: "ready",
|
|
message: null,
|
|
},
|
|
};
|
|
|
|
const wrapper = mount(DepartmentDashboardDailyReportProductSales, {
|
|
props: {
|
|
product_id: 24,
|
|
subtitle: "Dagens salg af produktet",
|
|
},
|
|
});
|
|
|
|
expect(wrapper.text()).toContain("Custom API Title");
|
|
expect(wrapper.text()).not.toContain("Spot Free (Lastbil)");
|
|
});
|
|
|
|
it("falls back to the configured product title when the overview title is blank or missing", () => {
|
|
dailyReportProductsState.value = {
|
|
24: {
|
|
title: " ",
|
|
value: 3,
|
|
out_of: 8,
|
|
state: "ready",
|
|
message: null,
|
|
},
|
|
};
|
|
|
|
const blankTitleWrapper = mount(DepartmentDashboardDailyReportProductSales, {
|
|
props: {
|
|
product_id: 24,
|
|
subtitle: "Dagens salg af produktet",
|
|
},
|
|
});
|
|
|
|
expect(blankTitleWrapper.text()).toContain("Spot Free (Lastbil)");
|
|
|
|
dailyReportProductsState.value = {};
|
|
|
|
const missingTileWrapper = mount(DepartmentDashboardDailyReportProductSales, {
|
|
props: {
|
|
product_id: 22,
|
|
subtitle: "Dagens salg af produktet",
|
|
},
|
|
});
|
|
|
|
expect(missingTileWrapper.text()).toContain("Tillæg for Specialsæbe - DD");
|
|
});
|
|
|
|
it("falls back to a brand-agnostic wax title for product 26 (TRU-130)", () => {
|
|
// TRU-130: the previous wax product ("Højglans - Voksforsegling") tested
|
|
// poorly in the field and is being replaced. The fallback title should
|
|
// therefore stay brand-agnostic so the tile keeps matching whatever
|
|
// wax product the API eventually reports.
|
|
dailyReportProductsState.value = {};
|
|
|
|
const waxWrapper = mount(DepartmentDashboardDailyReportProductSales, {
|
|
props: {
|
|
product_id: 26,
|
|
subtitle: "Dagens salg af produktet",
|
|
dataTestid: "daily-report-tile-hoejglans",
|
|
},
|
|
});
|
|
|
|
const rendered = waxWrapper.text();
|
|
expect(rendered).toContain("Voksforsegling pr. enhed");
|
|
expect(rendered).not.toContain("Højglans");
|
|
});
|
|
|
|
it("shows a saved target percentage below the product percentage", () => {
|
|
dailyReportProductsState.value = {
|
|
24: {
|
|
title: "Spot Free (Lastbil)",
|
|
value: 3,
|
|
out_of: 8,
|
|
state: "ready",
|
|
message: null,
|
|
target_percentage: 62.5,
|
|
target_department_id: 1,
|
|
},
|
|
};
|
|
|
|
const wrapper = mount(DepartmentDashboardDailyReportProductSales, {
|
|
props: {
|
|
product_id: 24,
|
|
subtitle: "Dagens salg af produktet",
|
|
dataTestid: "daily-report-tile-spot-free-lastbil",
|
|
},
|
|
});
|
|
|
|
expect(wrapper.get('[data-testid="daily-report-tile-spot-free-lastbil-target-button"]').text()).toContain("62.5%");
|
|
});
|
|
|
|
it("opens the target editor from the percentage button and saves through the daily report API", async () => {
|
|
selectedDepartmentIdsState.value = [1];
|
|
hasPermissionMock.mockImplementation((permission) => permission === "set_department_daily_report_product_targets");
|
|
setProductTargetMock.mockResolvedValue({ data: { data: { target_percentage: 70 } } });
|
|
dailyReportProductsState.value = {
|
|
24: {
|
|
title: "Spot Free (Lastbil)",
|
|
value: 3,
|
|
out_of: 8,
|
|
state: "ready",
|
|
message: null,
|
|
target_percentage: null,
|
|
target_department_id: null,
|
|
},
|
|
};
|
|
|
|
const wrapper = mount(DepartmentDashboardDailyReportProductSales, {
|
|
props: {
|
|
product_id: 24,
|
|
subtitle: "Dagens salg af produktet",
|
|
dataTestid: "daily-report-tile-spot-free-lastbil",
|
|
},
|
|
});
|
|
|
|
await wrapper.get('[data-testid="daily-report-tile-spot-free-lastbil-percentage-button"]').trigger("click");
|
|
await nextTick();
|
|
await wrapper.get('[data-testid="daily-report-tile-spot-free-lastbil-target-input"]').setValue("70");
|
|
await wrapper.find("form").trigger("submit");
|
|
await nextTick();
|
|
await Promise.resolve();
|
|
|
|
expect(setProductTargetMock).toHaveBeenCalledWith({
|
|
department_id: 1,
|
|
product_id: 24,
|
|
target_percentage: 70,
|
|
});
|
|
expect(refreshOverviewMock).toHaveBeenCalledTimes(1);
|
|
expect(dailyReportProductsState.value[24]).toMatchObject({
|
|
target_percentage: 70,
|
|
target_department_id: 1,
|
|
});
|
|
});
|
|
});
|