Files
pleno-vue/tests/unit/department-daily-report-product-sales.behavior.spec.js
Jeppe BandPleno Bugfix Bot 6f39eb897c fix(pleno-vue): make wax product title brand-agnostic (TRU-130) (#319)
## 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>
2026-08-16 21:40:07 +02:00

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,
});
});
});