Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d30273cda8 | ||
|
|
b893e69b54 | ||
|
|
eabb12ef97 | ||
|
|
225741cf7b |
@@ -34,6 +34,12 @@ export const sourceMappings = [
|
||||
],
|
||||
projects: chromiumProjects,
|
||||
},
|
||||
{
|
||||
name: "user-vehicles",
|
||||
patterns: [/^src\/components\/displays\/user\/vehicles\//u, /^src\/views\/dashboards\/userDashboard\/vehicles\//u],
|
||||
specs: ["tests/e2e/userVehicles.spec.ts"],
|
||||
projects: chromiumProjects,
|
||||
},
|
||||
{
|
||||
name: "pos",
|
||||
patterns: [/\/pos[/-]/iu, /POS/iu, /^src\/assets\/pos\.css$/u],
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script setup>
|
||||
import EditableTableColumn from "@/components/displays/buttons/EditableTableColumn.vue";
|
||||
import { ref } from "vue";
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { t } = useI18n();
|
||||
import { departments, getDepartments} from "@/components/pagination/departmentTabs.vue";
|
||||
import { departments, getDepartments } from "@/components/pagination/departmentTabs.vue";
|
||||
import { SessionUser } from "@/components/session/token/SessionUser.vue";
|
||||
import { loadList } from "@/components/pagination/paginatedList.vue";
|
||||
import ActionSettingsWheelButton from "@/components/displays/buttons/ActionSettingsWheelButton.vue";
|
||||
@@ -27,16 +27,15 @@ const props = defineProps({
|
||||
reloadList: {
|
||||
type: Function,
|
||||
required: false,
|
||||
default: null
|
||||
default: null,
|
||||
},
|
||||
add_other_customer_id: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: null
|
||||
}
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
const reload = () => {
|
||||
// Load the list of vehicles
|
||||
if (props.reloadList) {
|
||||
@@ -59,13 +58,9 @@ if (departments.value.length === 0) {
|
||||
const onClickListAddons = (vehicleId) => {
|
||||
// Redirect to the product addons page
|
||||
console.log("Fetching product addons for vehicle: " + vehicleId);
|
||||
SessionUser.request(
|
||||
'/vehicles/addons/available',
|
||||
'GET',
|
||||
{
|
||||
id: vehicleId
|
||||
},
|
||||
)
|
||||
SessionUser.request("/vehicles/addons/available", "GET", {
|
||||
id: vehicleId,
|
||||
});
|
||||
};
|
||||
|
||||
const vehicleAddons = ref(null);
|
||||
@@ -76,13 +71,9 @@ const getVehicleAvailableAddons = (vehicleId, forceReload = false) => {
|
||||
// Get the vehicle addons from the server
|
||||
if (vehicleAddons.value === null) {
|
||||
vehicleAddons.value = [];
|
||||
SessionUser.request(
|
||||
'/vehicles/addons/available',
|
||||
'GET',
|
||||
{
|
||||
id: vehicleId
|
||||
},
|
||||
).then((response) => {
|
||||
SessionUser.request("/vehicles/addons/available", "GET", {
|
||||
id: vehicleId,
|
||||
}).then((response) => {
|
||||
console.log("Vehicle addons: ", response.data.data);
|
||||
vehicleAddons.value = response.data.data;
|
||||
});
|
||||
@@ -93,14 +84,10 @@ const getVehicleAvailableAddons = (vehicleId, forceReload = false) => {
|
||||
|
||||
const toggleVehicleAddon = (vehicleId, addonId) => {
|
||||
// Toggle the vehicle addon
|
||||
SessionUser.request(
|
||||
'/vehicles/addons/toggle',
|
||||
'POST',
|
||||
{
|
||||
vehicle_id: vehicleId,
|
||||
addon_id: addonId
|
||||
},
|
||||
).then(() => {
|
||||
SessionUser.request("/vehicles/addons/toggle", "POST", {
|
||||
vehicle_id: vehicleId,
|
||||
addon_id: addonId,
|
||||
}).then(() => {
|
||||
reload();
|
||||
});
|
||||
};
|
||||
@@ -108,9 +95,9 @@ const toggleVehicleAddon = (vehicleId, addonId) => {
|
||||
const getVehicleAddonToggleIcon = (vehicleAddon) => {
|
||||
// Check if the vehicle addon is applied
|
||||
if (isVehicleAddonApplied(vehicleAddon)) {
|
||||
return 'fas fa-minus';
|
||||
return "fas fa-minus";
|
||||
} else {
|
||||
return 'fas fa-plus';
|
||||
return "fas fa-plus";
|
||||
}
|
||||
};
|
||||
|
||||
@@ -137,140 +124,142 @@ const getProductOptionsLabel = (vehicle) => {
|
||||
<div class="table-container" data-testid="user-vehicles-table-container">
|
||||
<table class="table is-fullwidth" data-testid="user-vehicles-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-if="!props.compact">{{ $t('objects.columns.id') }}</th>
|
||||
<th v-if="!props.compact">{{ $t('objects.columns.customer_id') }}</th>
|
||||
<th>{{ $t('objects.bookings.columns.reg_1') }}</th>
|
||||
<th>{{ $t('vehicles.type') }}</th>
|
||||
<th>{{ $t('objects.vehicles.columns.wash_subscription') }}</th>
|
||||
<th v-if="!props.compact">{{ SessionUser.objects.product_options.meta.title }}</th>
|
||||
<th v-if="!props.compact">{{ $t('common.reference') }}</th>
|
||||
<th v-if="!props.compact"></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th v-if="!props.compact">{{ $t("objects.columns.id") }}</th>
|
||||
<th v-if="!props.compact">{{ $t("objects.columns.customer_id") }}</th>
|
||||
<th>{{ $t("objects.bookings.columns.reg_1") }}</th>
|
||||
<th>{{ $t("vehicles.type") }}</th>
|
||||
<th>{{ $t("objects.vehicles.columns.wash_subscription") }}</th>
|
||||
<th v-if="!props.compact">{{ SessionUser.objects.product_options.meta.title }}</th>
|
||||
<th v-if="!props.compact">{{ $t("common.reference") }}</th>
|
||||
<th v-if="!props.compact"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="object in props.vehicles" :key="object.id">
|
||||
<!-- ID -->
|
||||
<EditableTableColumn
|
||||
v-if="!props.compact"
|
||||
:object="object"
|
||||
:loadList="reload"
|
||||
column="id"
|
||||
/>
|
||||
<!-- Customer ID -->
|
||||
<EditableTableColumn
|
||||
v-if="!props.compact"
|
||||
:object="object"
|
||||
:loadList="reload"
|
||||
column="customer_id"
|
||||
/>
|
||||
<!-- Reg -->
|
||||
<EditableTableColumn
|
||||
:object="object"
|
||||
:loadList="reload"
|
||||
:editFunction="SessionUser.objects.vehicles.showEditObjectFieldForm"
|
||||
column="reg"
|
||||
/>
|
||||
<!-- Type -->
|
||||
<EditableTableColumn
|
||||
:object="object"
|
||||
:loadList="reload"
|
||||
:editFunction="SessionUser.objects.vehicles.showEditObjectFieldForm"
|
||||
column="type"
|
||||
:parse-function="(value) => {
|
||||
return SessionUser.objects.products.functions.getProductName(value, 'Ukendt');
|
||||
}"
|
||||
/>
|
||||
<!-- Wash Subscription -->
|
||||
<EditableTableColumn
|
||||
:object="object"
|
||||
:loadList="reload"
|
||||
:editFunction="SessionUser.objects.vehicles.showEditObjectFieldForm"
|
||||
column="wash_subscription"
|
||||
:parse-function="(value) => {
|
||||
return value ? t('common.yes') : t('common.no');
|
||||
}"
|
||||
/>
|
||||
<!-- Product Options, if the wash subscription is set to true -->
|
||||
<td v-if="!props.compact">
|
||||
<template v-if="object.wash_subscription">
|
||||
<!-- Enabled subscription -->
|
||||
<ActionSettingsWheelButton
|
||||
:label="getProductOptionsLabel(object)"
|
||||
:icon="SessionUser.objects.product_options.meta.icon"
|
||||
@mouseenter="getVehicleAvailableAddons(object.id, true)"
|
||||
>
|
||||
<template #actions>
|
||||
<!-- List addons -->
|
||||
<template v-for="vehicleAddon in vehicleAddons" :key="vehicleAddon.id">
|
||||
<ActionSettingsWheelItem
|
||||
:label="(isVehicleAddonApplied(vehicleAddon) ? SessionUser.objects.global.language.remove : SessionUser.objects.global.language.add) + ' ' + vehicleAddon.name"
|
||||
:icon="getVehicleAddonToggleIcon(vehicleAddon)"
|
||||
:click-action="() => toggleVehicleAddon(object.id, vehicleAddon.id)"
|
||||
:template="isVehicleAddonApplied(vehicleAddon) ? 'danger' : 'default'"
|
||||
/>
|
||||
<tr v-for="object in props.vehicles" :key="object.id">
|
||||
<!-- ID -->
|
||||
<EditableTableColumn v-if="!props.compact" :object="object" :loadList="reload" column="id" />
|
||||
<!-- Customer ID -->
|
||||
<EditableTableColumn v-if="!props.compact" :object="object" :loadList="reload" column="customer_id" />
|
||||
<!-- Reg -->
|
||||
<EditableTableColumn
|
||||
:object="object"
|
||||
:loadList="reload"
|
||||
:editFunction="SessionUser.objects.vehicles.showEditObjectFieldForm"
|
||||
column="reg"
|
||||
/>
|
||||
<!-- Type -->
|
||||
<EditableTableColumn
|
||||
:object="object"
|
||||
:loadList="reload"
|
||||
:editFunction="SessionUser.objects.vehicles.showEditObjectFieldForm"
|
||||
column="type"
|
||||
:parse-function="
|
||||
(value) => {
|
||||
return SessionUser.objects.products.functions.getProductName(value, 'Ukendt');
|
||||
}
|
||||
"
|
||||
/>
|
||||
<!-- Wash Subscription -->
|
||||
<EditableTableColumn
|
||||
:object="object"
|
||||
:loadList="reload"
|
||||
:editFunction="SessionUser.objects.vehicles.showEditObjectFieldForm"
|
||||
column="wash_subscription"
|
||||
:parse-function="
|
||||
(value) => {
|
||||
return value ? t('common.yes') : t('common.no');
|
||||
}
|
||||
"
|
||||
/>
|
||||
<!-- Product Options, if the wash subscription is set to true -->
|
||||
<td v-if="!props.compact">
|
||||
<template v-if="object.wash_subscription">
|
||||
<!-- Enabled subscription -->
|
||||
<ActionSettingsWheelButton
|
||||
:label="getProductOptionsLabel(object)"
|
||||
:icon="SessionUser.objects.product_options.meta.icon"
|
||||
@mouseenter="getVehicleAvailableAddons(object.id, true)"
|
||||
>
|
||||
<template #actions>
|
||||
<!-- List addons -->
|
||||
<template v-for="vehicleAddon in vehicleAddons" :key="vehicleAddon.id">
|
||||
<ActionSettingsWheelItem
|
||||
:label="
|
||||
(isVehicleAddonApplied(vehicleAddon)
|
||||
? SessionUser.objects.global.language.remove
|
||||
: SessionUser.objects.global.language.add) +
|
||||
' ' +
|
||||
vehicleAddon.name
|
||||
"
|
||||
:icon="getVehicleAddonToggleIcon(vehicleAddon)"
|
||||
:click-action="() => toggleVehicleAddon(object.id, vehicleAddon.id)"
|
||||
:template="isVehicleAddonApplied(vehicleAddon) ? 'danger' : 'default'"
|
||||
/>
|
||||
</template>
|
||||
<!-- If there are no addons, show a message -->
|
||||
<ActionSettingsWheelItem
|
||||
v-if="vehicleAddons ? vehicleAddons.length === 0 : true"
|
||||
:label="$t('global.no_data')"
|
||||
icon="fas fa-list"
|
||||
/>
|
||||
<!-- Addons -->
|
||||
</template>
|
||||
</ActionSettingsWheelButton>
|
||||
</template>
|
||||
<!-- If there are no addons, show a message -->
|
||||
<ActionSettingsWheelItem
|
||||
v-if="vehicleAddons ? vehicleAddons.length === 0 : true"
|
||||
:label="$t('global.no_data')"
|
||||
icon="fas fa-list"
|
||||
/>
|
||||
<!-- Addons -->
|
||||
</template>
|
||||
</ActionSettingsWheelButton>
|
||||
</template>
|
||||
<template v-else>
|
||||
<!-- Disabled subscription -->
|
||||
{{ $t('global.no_data') }}
|
||||
</template>
|
||||
</td>
|
||||
<!-- Reference to the vehicle -->
|
||||
<EditableTableColumn
|
||||
v-if="!props.compact"
|
||||
:object="object"
|
||||
:loadList="reload"
|
||||
:editFunction="SessionUser.objects.vehicles.showEditObjectFieldForm"
|
||||
column="reference"
|
||||
/>
|
||||
<!-- Actions -->
|
||||
<td>
|
||||
<!-- Actions Should not be shown directly! -->
|
||||
<ActionSettingsWheelButton
|
||||
v-if="!props.compact"
|
||||
:user_id="object.user_id"
|
||||
:reg_1="object.reg"
|
||||
:displayActionsDirectly="false"
|
||||
>
|
||||
<template #actions>
|
||||
<!-- View (Redirect to the vehicle page) -->
|
||||
<ActionSettingsWheelItem
|
||||
:label="$t('global.manage') + ' ' + $t('objects.vehicles.single')"
|
||||
icon="fas fa-eye"
|
||||
:click-action="() => redirectUserVehiclePage(object.id)"
|
||||
/>
|
||||
<!-- Delete -->
|
||||
<ActionSettingsWheelItem
|
||||
:label="$t('global.delete') + ' ' + $t('objects.vehicles.single')"
|
||||
icon="fas fa-trash"
|
||||
:template="'danger'"
|
||||
:click-action="() => SessionUser.objects.vehicles.functions.showDeleteObjectForm(object.id, () => reload())"
|
||||
/>
|
||||
</template>
|
||||
</ActionSettingsWheelButton>
|
||||
</td>
|
||||
</tr>
|
||||
<template v-else>
|
||||
<!-- Disabled subscription -->
|
||||
{{ $t("global.no_data") }}
|
||||
</template>
|
||||
</td>
|
||||
<!-- Reference to the vehicle -->
|
||||
<EditableTableColumn
|
||||
v-if="!props.compact"
|
||||
:object="object"
|
||||
:loadList="reload"
|
||||
:editFunction="SessionUser.objects.vehicles.showEditObjectFieldForm"
|
||||
column="reference"
|
||||
/>
|
||||
<!-- Actions -->
|
||||
<td>
|
||||
<!-- Actions stay grouped behind the wheel menu. -->
|
||||
<ActionSettingsWheelButton
|
||||
v-if="!props.compact"
|
||||
:user_id="object.user_id"
|
||||
:reg_1="object.reg"
|
||||
:displayActionsDirectly="false"
|
||||
>
|
||||
<template #actions>
|
||||
<!-- View (Redirect to the vehicle page) -->
|
||||
<ActionSettingsWheelItem
|
||||
:label="$t('global.manage') + ' ' + $t('objects.vehicles.single')"
|
||||
icon="fas fa-eye"
|
||||
:click-action="() => redirectUserVehiclePage(object.id)"
|
||||
/>
|
||||
<!-- Delete -->
|
||||
<ActionSettingsWheelItem
|
||||
:label="$t('global.delete') + ' ' + $t('objects.vehicles.single')"
|
||||
icon="fas fa-trash"
|
||||
:template="'danger'"
|
||||
:click-action="
|
||||
() => SessionUser.objects.vehicles.functions.showDeleteObjectForm(object.id, () => reload())
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
</ActionSettingsWheelButton>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="10">{{ $t('tables.showing') }} {{ props.vehicles.length }} {{ $t('objects.vehicles.multiple') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="10">
|
||||
{{ $t("tables.showing") }} {{ props.vehicles.length }} {{ $t("objects.vehicles.multiple") }}
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { sourceMappings } from "../../scripts/playwright-pr-mapping.mjs";
|
||||
|
||||
const specsFor = (file) =>
|
||||
sourceMappings
|
||||
.filter((mapping) => mapping.patterns.some((pattern) => pattern.test(file)))
|
||||
.flatMap((mapping) => mapping.specs);
|
||||
|
||||
describe("Playwright PR mapping", () => {
|
||||
it("maps user vehicle table changes to the user vehicles E2E coverage", () => {
|
||||
expect(specsFor("src/components/displays/user/vehicles/vehiclesTable.vue")).toContain(
|
||||
"tests/e2e/userVehicles.spec.ts"
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -17,7 +17,8 @@ describe("self-serve studio condition scope", () => {
|
||||
'const nodeScopeLabel = (data) => scopeParts(data).join(" / ") || data?.subtitle || "Shared scope";'
|
||||
);
|
||||
expect(studioSource).toContain("v-else-if=\"selectedNode.data.kind === 'condition'\"");
|
||||
expect(studioSource).toContain("<div><dt>Scope</dt><dd>{{ nodeScopeLabel(data) }}</dd></div>");
|
||||
expect(studioSource).toContain("<dt>Scope</dt>");
|
||||
expect(studioSource).toContain("<dd>{{ nodeScopeLabel(data) }}</dd>");
|
||||
});
|
||||
|
||||
it("builds conditions with expression groups, branches, and cases", () => {
|
||||
|
||||
@@ -11,9 +11,17 @@ describe("self-serve studio managed inspector", () => {
|
||||
it("routes non-graph self-serve nodes to managed inspector panels", () => {
|
||||
const source = studioSource();
|
||||
|
||||
expect(source).toContain(
|
||||
'const managedInspectorNodeKinds = ["runtime_checkpoint", "vehicle_type", "machine_type", "edge_gateway", "relay_binding", "relay"];'
|
||||
);
|
||||
expect(source).toContain("const managedInspectorNodeKinds = [");
|
||||
for (const nodeKind of [
|
||||
"runtime_checkpoint",
|
||||
"vehicle_type",
|
||||
"machine_type",
|
||||
"edge_gateway",
|
||||
"relay_binding",
|
||||
"relay",
|
||||
]) {
|
||||
expect(source).toContain(`"${nodeKind}"`);
|
||||
}
|
||||
expect(source).toContain('v-else-if="isSelectedManaged"');
|
||||
expect(source).toContain('data-testid="studio-managed-inspector"');
|
||||
expect(source).toContain('data-testid="studio-runtime-inspector"');
|
||||
@@ -42,7 +50,8 @@ describe("self-serve studio managed inspector", () => {
|
||||
expect(source).toContain('return applyVirtualHardwareOperation("upsert_gateway", {');
|
||||
expect(source).toContain("const saveSelectedVirtualBinding = () => {");
|
||||
expect(source).toContain('return applyVirtualHardwareOperation("upsert_binding", payload);');
|
||||
expect(source).toContain("const deleteSelectedVirtualBinding = () => deleteVirtualBinding({");
|
||||
expect(source).toContain("const deleteSelectedVirtualBinding = () =>");
|
||||
expect(source).toContain("deleteVirtualBinding({");
|
||||
expect(source).toContain("const selectedGatewayBindingEntries = computed(() => {");
|
||||
expect(source).toContain("const selectedRelayBindingEntries = computed(() => {");
|
||||
});
|
||||
|
||||
@@ -46,9 +46,8 @@ describe("self-serve studio task editing", () => {
|
||||
const source = studioSource();
|
||||
|
||||
expect(source).toContain("getSelfServeDynamicImageButtonsToPress(simulatorPreviewTasks.value)");
|
||||
expect(source).toContain(
|
||||
"const simulatorDynamicImageCurrentStep = computed(() => getSelfServeCompletedDynamicImageStep("
|
||||
);
|
||||
expect(source).toContain("const simulatorDynamicImageCurrentStep = computed(() =>");
|
||||
expect(source).toContain("getSelfServeCompletedDynamicImageStep(");
|
||||
expect(source).toContain("const simulatorDynamicImageUrl = computed(() => {");
|
||||
expect(source).toContain("buildSelfServeDynamicImageUrl({");
|
||||
expect(source).toContain("currentStep: simulatorDynamicImageCurrentStep.value");
|
||||
@@ -77,10 +76,10 @@ describe("self-serve studio task editing", () => {
|
||||
const source = studioSource();
|
||||
|
||||
expect(source).toContain("const scopeFiltersFromRoute = () => ({");
|
||||
expect(source).toContain("const showScopeGuide = computed(() => (");
|
||||
expect(source).toContain("const showScopeGuide = computed(");
|
||||
expect(source).toContain("const applyScopeGuideSelection = () => {");
|
||||
expect(source).toContain("const writeScopeToRoute = () => {");
|
||||
expect(source).toContain("const simulatorStorageKey = computed(() => [");
|
||||
expect(source).toContain("const simulatorStorageKey = computed(() =>");
|
||||
expect(source).toContain("const restoreSimulatorStateFromStorage = () => {");
|
||||
expect(source).toContain("const persistSimulatorStateToStorage = () => {");
|
||||
expect(source).toContain('data-testid="studio-scope-guide"');
|
||||
@@ -93,7 +92,7 @@ describe("self-serve studio task editing", () => {
|
||||
const source = studioSource();
|
||||
|
||||
expect(source).toContain("const taskAttachmentList = (dataOrRaw) => {");
|
||||
expect(source).toContain("const selectedTaskAttachments = computed(() => (");
|
||||
expect(source).toContain("const selectedTaskAttachments = computed(() =>");
|
||||
expect(source).toContain("const uploadSelectedTaskAttachment = async (event) => {");
|
||||
expect(source).toContain("const deleteSelectedTaskAttachment = async (attachment) => {");
|
||||
expect(source).toContain('data-testid="studio-task-node-attachments"');
|
||||
|
||||
@@ -42,7 +42,7 @@ describe("self-serve studio vehicle type scope", () => {
|
||||
|
||||
expect(source).toContain("const selectedSimulatorLaneId = computed(() => firstPositiveInt(");
|
||||
expect(source).toContain("filters.value.lane_id");
|
||||
expect(source).toContain("const selectedSimulatorVehicleTypeId = computed(() => (");
|
||||
expect(source).toContain("const selectedSimulatorVehicleTypeId = computed(() =>");
|
||||
expect(source).toContain("vehicleTypeMatchesFilter(vehicleType, selectedValue)");
|
||||
expect(source).toContain("lane_id: laneId");
|
||||
expect(source).toContain("vehicle_type_id: selectedSimulatorVehicleTypeId.value");
|
||||
|
||||
Reference in New Issue
Block a user