const formState = ref<AddStationsFormState>(makeInitialState())
const pending = ref(false)
- watch($templates, () => {
- formState.value.renderTemplates = randomUUID()
+ watch($templates, (newTemplates, oldTemplates) => {
+ // Only regenerate the key when the template list content actually changes.
+ // This prevents destroying the user's current selection when an in-flight
+ // refresh returns the same set of templates.
+ if (!templatesEqual(newTemplates, oldTemplates)) {
+ formState.value.renderTemplates = randomUUID()
+ }
})
/** Resets form state to initial defaults. */
function nonEmpty (value: string): string | undefined {
return value.length > 0 ? value : undefined
}
+
+/**
+ * Returns `true` when both arrays have identical length and values in the same order.
+ * @param a - The incoming template list.
+ * @param b - The previous template list.
+ * @returns Whether the two arrays are deeply equal.
+ */
+function templatesEqual (a: string[], b: string[] | undefined): boolean {
+ if (a.length !== b?.length) return false
+ return a.every((v, i) => v === b[i])
+}
getData: () => void
/** Fetches only the simulator state. */
getSimulatorState: () => void
+ /** Fetches only the charging station templates list. */
+ getTemplates: () => void
/** Whether any data fetch is currently in progress. */
loading: ComputedRef<boolean>
/** Registers WS event listeners for open/error/close. */
getChargingStations,
getData,
getSimulatorState,
+ getTemplates,
loading,
// Exposed for edge cases (e.g. hot-reload); normally called via onMounted/onUnmounted.
registerWSEventListeners,
import Container from './components/ClassicContainer.vue'
const layoutData = useLayoutData()
-const { simulatorStarted, simulatorState, uiServerConfigurations } = layoutData
+const { getTemplates, simulatorStarted, simulatorState, uiServerConfigurations } = layoutData
const startSimulatorLabel = computed(
() =>
name => {
if (name === ROUTE_NAMES.CHARGING_STATIONS) {
refresh()
+ } else if (name === ROUTE_NAMES.ADD_CHARGING_STATIONS) {
+ getTemplates()
}
}
)
<script setup lang="ts">
// Dialog state via v-if (no URL coupling), enabling skin-independent modal interactions.
import { type OCPPVersion } from 'ui-common'
-import { defineAsyncComponent, ref } from 'vue'
+import { defineAsyncComponent, ref, watch } from 'vue'
import {
ASYNC_COMPONENT_DELAY_MS,
const $chargingStations = useChargingStations()
const layoutData = useLayoutData()
-const { simulatorState, uiServerConfigurations } = layoutData
+const { getTemplates, simulatorState, uiServerConfigurations } = layoutData
const uiServerIndex = ref(getFromLocalStorage<number>(UI_SERVER_CONFIGURATION_INDEX_KEY, 0))
const confirmingStopSim = ref(false)
const showAddDialog = ref(false)
+
+watch(showAddDialog, open => {
+ if (open) getTemplates()
+})
const showSetUrlDialog = ref<null | {
chargingStationId: string
hashId: string
expect(mockAddChargingStations).toHaveBeenCalledWith('boundary.json', 0, expect.any(Object))
})
+ it('should not update renderTemplates when refetch returns identical template list', async () => {
+ const { formState } = useAddStationsForm()
+ const before = formState.value.renderTemplates
+ // Assign a new array with the same content to $templates
+ mockTemplates.value = ['template1.json', 'template2.json']
+ await nextTick()
+ expect(formState.value.renderTemplates).toBe(before)
+ })
+
it('should update renderTemplates reactively when templates ref changes', async () => {
const { formState } = useAddStationsForm()
const initial = formState.value.renderTemplates
* @returns Mounted wrapper for AddStationsDialog
*/
function mountDialog (templates = ['template-A.json', 'template-B.json']) {
+ mockClient.listTemplates.mockResolvedValue({ status: ResponseStatus.SUCCESS, templates })
return mount(AddStationsDialog, {
global: {
provide: {