CSS/Vue-only polish of the #993 Show details view: classic panel-placement fix + section/key-value separation; modern section panels, promoted titles and tighter symmetric row separators; not-found escape; toggle-navigation test coverage.
Show Details
</h1>
<h2>{{ chargingStationId }}</h2>
- <p
- v-if="station == null"
- class="show-details__empty"
- >
- Charging station not found
- </p>
+ <template v-if="station == null">
+ <p class="show-details__empty">
+ Charging station not found
+ </p>
+ <Button
+ id="action-button"
+ @click="close()"
+ >
+ Back to Charging Stations
+ </Button>
+ </template>
<template v-else>
<table
v-for="section in sections"
</tbody>
</table>
</template>
- <Button
- id="action-button"
- @click="close()"
- >
- Close
- </Button>
</template>
<script setup lang="ts">
</script>
<style scoped>
+/* Bound the width: the shared action container is `min-width: max-content`,
+ * so uncapped these tables would grow it to fill the main area. */
.show-details__section {
- margin-bottom: var(--spacing-md);
+ width: 32rem;
+ margin-bottom: var(--spacing-lg);
+}
+
+.show-details__section:last-of-type {
+ margin-bottom: 0;
}
.show-details__section :is(th, td) {
text-align: left;
+ vertical-align: top;
+ overflow-wrap: anywhere;
+}
+
+.show-details__section th[scope='row'] {
+ font-weight: bold;
+ background-color: var(--color-bg-header);
+ border-right: solid 0.25px var(--color-border);
}
.show-details__empty {
display: flex;
flex-direction: column;
gap: var(--skin-space-2);
+ padding: var(--skin-space-3);
+ background-color: var(--skin-surface-sunken);
+ border: 1px solid var(--skin-border);
+ border-radius: var(--skin-radius-lg);
+}
+
+.station-details__section > h3 {
+ font-size: 0.8125rem;
+ color: var(--color-text-strong);
+ padding-bottom: var(--skin-space-2);
+ border-bottom: 1px solid var(--skin-border);
}
.station-details__list {
margin: 0;
display: grid;
- gap: var(--skin-space-2);
+ gap: var(--skin-space-1);
}
.station-details__row {
align-items: baseline;
}
+.station-details__row + .station-details__row {
+ padding-top: var(--skin-space-1);
+ border-top: 1px solid var(--skin-border);
+}
+
.station-details__row dt {
margin: 0;
font-size: 0.6875rem;
})
describe('ShowDetails', () => {
- beforeEach(() => {
- mockPush.mockClear()
- })
-
afterEach(() => {
vi.clearAllMocks()
vi.restoreAllMocks()
expect(wrapper.text()).toContain('Charging station not found')
})
- it('should navigate to charging-stations on close', async () => {
- const wrapper = mountShowDetails()
+ it('should navigate to charging-stations from the not-found panel', async () => {
+ const wrapper = mountShowDetails([])
await wrapper.findComponent(ButtonStub).trigger('click')
await flushPromises()
expect(mockPush).toHaveBeenCalledWith({ name: 'charging-stations' })
toggleProps.off?.()
expect(mockPush).toHaveBeenCalledWith({ name: 'charging-stations' })
})
+
+ it('should trigger router push to show-details on toggle on', () => {
+ const wrapper = mountCSData()
+ const toggleButtons = wrapper.findAllComponents(ToggleButtonStub)
+ expect(toggleButtons[1].props('id')).toBe(`${TEST_HASH_ID}-show-details`)
+ const toggleProps = toggleButtons[1].props() as unknown as StubProps
+ toggleProps.on?.()
+ expect(mockPush).toHaveBeenCalledOnce()
+ const callArg = mockPush.mock.calls[0][0] as { name: string; params: Record<string, string> }
+ expect(callArg.name).toBe('show-details')
+ expect(callArg.params.hashId).toBe(TEST_HASH_ID)
+ expect(callArg.params.chargingStationId).toBe(TEST_STATION_ID)
+ })
+
+ it('should trigger router push to charging-stations on show-details toggle off', () => {
+ const wrapper = mountCSData()
+ const toggleButtons = wrapper.findAllComponents(ToggleButtonStub)
+ expect(toggleButtons[1].props('id')).toBe(`${TEST_HASH_ID}-show-details`)
+ const toggleProps = toggleButtons[1].props() as unknown as StubProps
+ toggleProps.off?.()
+ expect(mockPush).toHaveBeenCalledWith({ name: 'charging-stations' })
+ })
})
})