Skip to content

Commit 21c32c9

Browse files
author
ivelin
committed
fix: polish UI. Cleaner render of idle snapshots / no detection.
Signed-off-by: ivelin <[email protected]>
1 parent c326fa4 commit 21c32c9

File tree

5 files changed

+217
-13
lines changed

5 files changed

+217
-13
lines changed

src/components/AppFrame.vue

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010
<v-main>
1111
<v-container
1212
id="app-container"
13-
class="pa-0 ma-0"
1413
fluid
14+
dense
15+
class="pa-0 ma-0"
1516
>
1617
<slot>
1718
<p>App page content goes here...</p>

src/components/EventCard.vue

+33-6
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
clipped
4343
dense
4444
>
45+
<!-- TODO: Implement event actions
4546
<v-timeline-item
4647
hide-dot
4748
v-if="data.args.inference_result.length > 0"
@@ -114,6 +115,7 @@
114115
</v-col>
115116
</v-row>
116117
</v-timeline-item>
118+
-->
117119
<v-timeline-item
118120
:color="eventColor(data.priority)"
119121
small
@@ -122,19 +124,44 @@
122124
<v-col cols="3">
123125
<strong>{{ friendlyTime(data.args.datetime) }}</strong>
124126
</v-col>
125-
<v-col>
126-
<div class="subtitle-2">
127+
<v-col
128+
v-if="data.args.inference_result && data.args.inference_result.length > 0"
129+
>
130+
<div
131+
class="subtitle-2"
132+
ref="event-title"
133+
>
127134
{{ data.message }}
128135
</div>
129-
<div class="body-2">
130-
{{ data.pipeline_display_name }} -
136+
<div
137+
class="body-2"
138+
ref="event-display-name"
139+
>
140+
{{ data.pipeline_display_name ? data.pipeline_display_name + ' - ' : '' }}
131141
{{ data.args.inference_meta.display }}
132142
</div>
133143
</v-col>
144+
<v-col
145+
v-else
146+
>
147+
<div
148+
class="subtitle-2"
149+
ref="event-title"
150+
>
151+
Idle Snapshot
152+
</div>
153+
<div
154+
class="body-2"
155+
ref="event-display-name"
156+
>
157+
No {{ data.args.inference_meta.display }}
158+
</div>
159+
</v-col>
134160
</v-row>
135161
</v-timeline-item>
136162

137163
<v-timeline-item
164+
ref="inf-item"
138165
color="teal lighten-3"
139166
small
140167
v-for="(inf, inf_index) in data.args.inference_result"
@@ -143,10 +170,10 @@
143170
>
144171
<v-row class="pt-1">
145172
<v-col cols="3">
146-
<strong>{{ inf.label }}</strong>
173+
<strong data-testid="inf-label">{{ inf.label }}</strong>
147174
</v-col>
148175
<v-col>
149-
<strong>{{ asPercentage(inf.confidence) }} confidence</strong>
176+
<strong data-testid="inf-score">{{ asPercentage(inf.confidence) }} confidence</strong>
150177
</v-col>
151178
</v-row>
152179
</v-timeline-item>

src/views/AddDevice.vue

+1-2
Original file line numberDiff line numberDiff line change
@@ -206,8 +206,7 @@
206206
</v-card-title>
207207
<v-card-text>
208208
<p class="text-left">
209-
Device successfully added to your list of managed devices.
210-
Continue to device timeline or configure settings.
209+
Device information saved.
211210
</p>
212211
</v-card-text>
213212
<v-card-actions>

src/views/Event.vue

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
<template>
22
<amb-app-frame>
33
<v-row
4-
align="center"
4+
align="start"
5+
v-if="this.edgeDeviceError"
56
>
67
<v-col
78
cols="12"
8-
class="ma-0 pa-0"
99
>
1010
<v-alert
11-
v-if="this.edgeDeviceError"
1211
outlined
1312
type="warning"
1413
dense
@@ -27,13 +26,14 @@
2726
align="start"
2827
justify="center"
2928
dense
29+
v-else
3030
>
3131
<v-col
3232
:style="maxWidth"
3333
align="center"
3434
justify="center"
3535
cols="12"
36-
class="pa-0 ma-0 fill-height"
36+
class="pa-0 ma-0 fill-height fill-width"
3737
>
3838
<v-card class="text-center">
3939
<event-card
+177
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import { createLocalVue, mount } from '@vue/test-utils'
2+
import { pnpStoreModule } from '@/store/pnp.js'
3+
import snackBarModule from '@/store/status-snackbar'
4+
import { myDevicesStoreModule } from '@/store/mydevices'
5+
import { cloneDeep } from 'lodash'
6+
import EventCard from '@/components/EventCard.vue'
7+
import Vuetify from 'vuetify'
8+
import Vuex from 'vuex'
9+
import Vue from 'vue'
10+
import VueRouter from 'vue-router'
11+
import flushPromises from 'flush-promises'
12+
13+
jest.mock('peerjs')
14+
15+
// global import instead of via localVue due to Vuetify 2 Typescript issue:
16+
// ref: https://vuetifyjs.com/en/getting-started/unit-testing/#bootstrapping-vuetify
17+
Vue.use(Vuetify)
18+
19+
const localVue = createLocalVue()
20+
21+
localVue.use(Vuex)
22+
// not recommended to use Vuetify with localVue. See note above.
23+
// localVue.use(Vuetify)
24+
localVue.use(VueRouter)
25+
26+
describe('Event Card Component', () => {
27+
let wrapper
28+
let store
29+
let vuetify
30+
let router
31+
32+
beforeEach(() => {
33+
vuetify = new Vuetify()
34+
35+
router = new VueRouter()
36+
37+
myDevicesStoreModule.actions.syncState = jest.fn()
38+
39+
store = new Vuex.Store({
40+
modules: {
41+
pnp: cloneDeep(pnpStoreModule),
42+
snackBar: cloneDeep(snackBarModule),
43+
myDevices: cloneDeep(myDevicesStoreModule)
44+
}
45+
})
46+
47+
store.state.pnp.edgeAPI = jest.fn()
48+
store.state.pnp.edgeAPI.getLocalImageURL = jest.fn()
49+
})
50+
51+
afterEach(() => {
52+
// wrapper.destroy()
53+
})
54+
55+
test('Event card should display inference labels', async () => {
56+
// event data
57+
const args = {
58+
datetime: '2020-05-10T19:05:45.577145',
59+
id: 'dde10cb4c3d74e828c473aa183cc8d80',
60+
image_file_name: '20200510-190545.577145-image.jpg',
61+
inference_meta: {
62+
display: 'Object Detection'
63+
},
64+
inference_result: [
65+
{
66+
box: {
67+
xmax: 0.7228575944900513,
68+
xmin: 0.3868940770626068,
69+
ymax: 1,
70+
ymin: 0.12535724414170846
71+
},
72+
confidence: 0.9921875,
73+
label: 'person'
74+
}
75+
],
76+
json_file_name: '20200510-190545.577145-inference.json',
77+
rel_dir: 'detections/20200510-190544.936209',
78+
thumbnail_file_name: '20200510-190545.577145-thumbnail.jpg'
79+
}
80+
81+
// create a parent dom for infinite-loader
82+
const div = document.createElement('div')
83+
document.body.appendChild(div)
84+
85+
wrapper = mount(EventCard, {
86+
router,
87+
store,
88+
vuetify,
89+
localVue,
90+
attachTo: div,
91+
propsData: {
92+
data: {
93+
priority: 'INFO',
94+
message: 'Detection Event',
95+
args
96+
}
97+
}
98+
})
99+
100+
// wait for the view to load async data and finish rendering
101+
await Vue.nextTick()
102+
await flushPromises()
103+
104+
// const html = wrapper.html()
105+
// console.debug('Event.vue HTML:', { html })
106+
107+
const eventTitle = wrapper.findComponent({ ref: 'event-title' })
108+
expect(eventTitle.exists()).toBeTrue()
109+
expect(eventTitle.text()).toContain('Detection Event')
110+
const eventName = wrapper.findComponent({ ref: 'event-display-name' })
111+
expect(eventName.exists()).toBeTrue()
112+
expect(eventName.text()).toContain('Object Detection')
113+
const infItems = wrapper.findAllComponents({ ref: 'inf-item' })
114+
expect(infItems).toHaveLength(1)
115+
const infItem = infItems.at(0)
116+
expect(infItem.exists()).toBeTrue()
117+
const infLabel = infItem.find('[data-testid="inf-label"]')
118+
// console.debug('infLabel HTML[' + infLabel.html() + ']')
119+
// console.debug({ infLabel })
120+
expect(infLabel.exists()).toBeTrue()
121+
expect(infLabel.text()).toBe('person')
122+
const infScore = infItem.find('[data-testid="inf-score"]')
123+
expect(infScore.exists()).toBeTrue()
124+
expect(infScore.text()).toContain('99% confidence')
125+
})
126+
127+
test('Event card shows idle snapshot events', async () => {
128+
// idle snapshot event data without inference
129+
const args = {
130+
datetime: '2020-05-10T19:05:45.577145',
131+
id: 'dde10cb4c3d74e828c473aa183cc8d80',
132+
image_file_name: '20200510-190545.577145-image.jpg',
133+
inference_meta: {
134+
display: 'Object Detection'
135+
},
136+
inference_result: [],
137+
json_file_name: '20200510-190545.577145-inference.json',
138+
rel_dir: 'detections/20200510-190544.936209',
139+
thumbnail_file_name: '20200510-190545.577145-thumbnail.jpg'
140+
}
141+
142+
// create a parent dom for infinite-loader
143+
const div = document.createElement('div')
144+
document.body.appendChild(div)
145+
146+
wrapper = mount(EventCard, {
147+
router,
148+
store,
149+
vuetify,
150+
localVue,
151+
attachTo: div,
152+
propsData: {
153+
data: {
154+
priority: 'INFO',
155+
message: 'Detection Event',
156+
args
157+
}
158+
}
159+
})
160+
161+
// wait for the view to load async data and finish rendering
162+
await Vue.nextTick()
163+
await flushPromises()
164+
165+
// const html = wrapper.html()
166+
// console.debug('Event.vue HTML:', { html })
167+
168+
const eventTitle = wrapper.findComponent({ ref: 'event-title' })
169+
expect(eventTitle.exists()).toBeTrue()
170+
expect(eventTitle.text()).toContain('Idle Snapshot')
171+
const eventName = wrapper.findComponent({ ref: 'event-display-name' })
172+
expect(eventName.exists()).toBeTrue()
173+
expect(eventName.text()).toContain('No Object Detection')
174+
const infItems = wrapper.findAllComponents({ ref: 'inf-item' })
175+
expect(infItems).toHaveLength(0)
176+
})
177+
})

0 commit comments

Comments
 (0)