Skip to content

Commit 980dcc6

Browse files
Feature/DF-861 QA fixes (#361)
* Geospatial list - render disabled action links * Fixes issue where the enter/return keypress activates the map search * Revert change that treated an empty string as an empty array * Fix geospatial joi tests * Code coverage * Add test coverage for readonly/disabled list * createFeaturesHTML tests * Re-render list on cancel * Code coverage * Code coverage
1 parent 2c838a4 commit 980dcc6

File tree

6 files changed

+168
-22
lines changed

6 files changed

+168
-22
lines changed

src/client/javascripts/geospatial-map.js

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -164,11 +164,17 @@ export function addFeatureToMap(feature, drawPlugin, map) {
164164
* Returns HTML summary list for the features
165165
* @param {FeatureCollection} features - the features
166166
* @param {string} mapId - the ID of the map
167+
* @param {boolean} [disabled] - render the list with disabled links
167168
* @param {boolean} [readonly] - render the list in readonly mode
168169
*/
169-
export function createFeaturesHTML(features, mapId, readonly = false) {
170+
export function createFeaturesHTML(
171+
features,
172+
mapId,
173+
disabled = false,
174+
readonly = false
175+
) {
170176
return `<dl class="govuk-summary-list">
171-
${features.map((feature, index) => createFeatureHTML(feature, index, mapId, readonly)).join('\n')}
177+
${features.map((feature, index) => createFeatureHTML(feature, index, mapId, disabled, readonly)).join('\n')}
172178
</dl>`
173179
}
174180

@@ -186,9 +192,16 @@ export function focusFeature(feature, mapProvider) {
186192
* @param {Feature} feature - the geo feature
187193
* @param {number} index - the feature index
188194
* @param {string} mapId - the ID of the map
189-
* @param {boolean} readonly - render the list item in readonly mode
195+
* @param {boolean} [disabled] - render the list with disabled links
196+
* @param {boolean} [readonly] - render the list item in readonly mode
190197
*/
191-
function createFeatureHTML(feature, index, mapId, readonly) {
198+
export function createFeatureHTML(
199+
feature,
200+
index,
201+
mapId,
202+
disabled = false,
203+
readonly = false
204+
) {
192205
const flattened = feature.geometry.coordinates.flat(2)
193206

194207
const points = []
@@ -203,13 +216,13 @@ function createFeatureHTML(feature, index, mapId, readonly) {
203216

204217
// Change action link
205218
const changeAction = () => `<li class="govuk-summary-list__actions-list-item">
206-
<a class="govuk-link govuk-link--no-visited-state" href="#${mapId}" data-action="edit" data-id="${feature.id}"
219+
<a class="govuk-link govuk-link--no-visited-state ${disabled ? 'govuk-link--disabled' : ''}" href="#${mapId}" data-action="edit" data-id="${feature.id}"
207220
data-type="${feature.geometry.type}">Update<span class="govuk-visually-hidden"> location</span></a>
208221
</li>`
209222

210223
// Delete action link
211224
const deleteAction = () => `<li class="govuk-summary-list__actions-list-item">
212-
<a class="govuk-link govuk-link--no-visited-state" href="#" data-action="delete" data-id="${feature.id}"
225+
<a class="govuk-link govuk-link--no-visited-state ${disabled ? 'govuk-link--disabled' : ''}" href="#" data-action="delete" data-id="${feature.id}"
213226
data-type="${feature.geometry.type}">Delete<span class="govuk-visually-hidden"> location</span></a>
214227
</li>`
215228

@@ -415,8 +428,8 @@ function getFeaturesManager(geojson) {
415428
* @returns {RenderList}
416429
*/
417430
function getListRenderer(geojson, mapId, listEl, renderValue) {
418-
return function renderList() {
419-
const html = createFeaturesHTML(geojson.features, mapId)
431+
return function renderList(disabled = false) {
432+
const html = createFeaturesHTML(geojson.features, mapId, disabled)
420433

421434
listEl.innerHTML = html
422435

@@ -523,7 +536,7 @@ function createContainers(geospatialInput, index) {
523536
function onMapReadyFactory(context) {
524537
const { map, activeFeatureManager, uiManager, interactPlugin, drawPlugin } =
525538
context
526-
const { toggleActionButtons } = uiManager
539+
const { toggleActionButtons, renderList } = uiManager
527540
const { resetActiveFeature } = activeFeatureManager
528541

529542
/**
@@ -542,6 +555,7 @@ function onMapReadyFactory(context) {
542555
onClick: () => {
543556
resetActiveFeature()
544557
toggleActionButtons(true)
558+
renderList(true)
545559
interactPlugin.enable()
546560
},
547561
mobile: { slot: 'actions' },
@@ -556,6 +570,7 @@ function onMapReadyFactory(context) {
556570
onClick: () => {
557571
resetActiveFeature()
558572
toggleActionButtons(true)
573+
renderList(true)
559574
drawPlugin.newPolygon(generateID(), polygonFeatureProperties)
560575
},
561576
mobile: { slot: 'actions' },
@@ -570,6 +585,7 @@ function onMapReadyFactory(context) {
570585
onClick: () => {
571586
resetActiveFeature()
572587
toggleActionButtons(true)
588+
renderList(true)
573589
drawPlugin.newLine(generateID(), lineFeatureProperties)
574590
},
575591
mobile: { slot: 'actions' },
@@ -589,6 +605,7 @@ function onMapReadyFactory(context) {
589605
const { listEl } = uiManager
590606
listEl.addEventListener('click', onListElClickFactory(context), false)
591607
listEl.addEventListener('change', onListElChangeFactory(context), false)
608+
listEl.addEventListener('keydown', onListElKeydownFactory(), false)
592609
}
593610
}
594611

@@ -679,7 +696,7 @@ function onDrawEditedFactory(context) {
679696
*/
680697
function onDrawCancelledFactory(context) {
681698
const { uiManager, activeFeatureManager } = context
682-
const { toggleActionButtons } = uiManager
699+
const { toggleActionButtons, renderList } = uiManager
683700
const { resetActiveFeature } = activeFeatureManager
684701

685702
/**
@@ -688,6 +705,7 @@ function onDrawCancelledFactory(context) {
688705
return function onDrawCancelled() {
689706
toggleActionButtons(false)
690707
resetActiveFeature()
708+
renderList()
691709
}
692710
}
693711

@@ -815,6 +833,7 @@ function onListElClickFactory(context) {
815833
}
816834

817835
toggleActionButtons(true)
836+
renderList(true)
818837
}
819838

820839
/**
@@ -859,7 +878,7 @@ function onListElClickFactory(context) {
859878
}
860879

861880
/**
862-
* Callback factory function that fires a 'change' event is fired on the list container
881+
* Callback factory function that fires when a 'change' event is fired on the list container
863882
* @param {Context} context - the UI context
864883
*/
865884
function onListElChangeFactory(context) {
@@ -891,6 +910,29 @@ function onListElChangeFactory(context) {
891910
}
892911
}
893912

913+
/**
914+
* Callback factory function that fires when a 'keydown' event is fired on the list container
915+
*/
916+
function onListElKeydownFactory() {
917+
/**
918+
* List container delegated 'keydown' events handler
919+
* Fixes the issue of pressing "Enter" key in the description input triggering the map search
920+
* @param {KeyboardEvent} e
921+
*/
922+
return function (e) {
923+
const target = e.target
924+
925+
if (!(target instanceof HTMLInputElement)) {
926+
return
927+
}
928+
929+
if (e.code === 'Enter' || e.code === 'NumpadEnter') {
930+
e.preventDefault()
931+
e.stopPropagation()
932+
}
933+
}
934+
}
935+
894936
/**
895937
* @import { MapsEnvironmentConfig, InteractiveMap } from '~/src/client/javascripts/map.js'
896938
*/
@@ -960,6 +1002,7 @@ function onListElChangeFactory(context) {
9601002
/**
9611003
* Renders the features into the list
9621004
* @callback RenderList
1005+
* @param {boolean} [disabled] - whether to render the list with disabled links
9631006
* @returns {void}
9641007
*/
9651008

src/client/stylesheets/shared.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@
3333
@include govuk-font($size: 19);
3434
}
3535

36+
// Used in geospatial field
37+
.govuk-link--disabled {
38+
opacity: 0.5;
39+
}
40+
3641
// Hide urls for hyperlinks when in print mode
3742
@media print {
3843
.govuk-link[href]::after {

src/server/plugins/engine/components/GeospatialField.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ describe('GeospatialField', () => {
110110

111111
expect(result.errors).toEqual([
112112
expect.objectContaining({
113-
text: 'Example geospatial must contain at least 1 items'
113+
text: 'Select example geospatial'
114114
})
115115
])
116116
})
@@ -128,7 +128,7 @@ describe('GeospatialField', () => {
128128

129129
expect(result.errors).toEqual([
130130
expect.objectContaining({
131-
text: 'Example geospatial title must contain at least 1 items'
131+
text: 'Select example geospatial title'
132132
})
133133
])
134134
})

src/server/plugins/engine/components/helpers/geospatial.test.js

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,7 @@ describe('Geospatial validation helpers', () => {
4949
test('it should validate an empty string', () => {
5050
const result = geospatialSchema.validate('')
5151

52-
expect(result.error).toBeUndefined()
53-
expect(result.value).toEqual([])
54-
})
55-
56-
test('it should validate an empty string with errors when required', () => {
57-
const result = geospatialSchema.min(1).required().validate('')
58-
5952
expect(result.error).toBeDefined()
60-
expect(result.value).toEqual([])
53+
expect(result.value).toBeUndefined()
6154
})
6255
})

src/server/plugins/engine/components/helpers/geospatial.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const Joi = JoiBase.extend({
2020
if (typeof value === 'string') {
2121
if (value.trim() === '') {
2222
return {
23-
value: []
23+
value: undefined
2424
}
2525
}
2626

test/client/javascripts/map.test.js

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import {
2+
createFeatureHTML,
3+
createFeaturesHTML
4+
} from '~/src/client/javascripts/geospatial-map.js'
15
import {
26
formSubmitFactory,
37
getCentroidGridRef,
@@ -1199,6 +1203,107 @@ describe('Maps Client JS', () => {
11991203
...features.slice(1)
12001204
])
12011205
})
1206+
1207+
test('description enter/return key', () => {
1208+
const { geospatialInput, listContainer } = initialiseGeospatialMaps()
1209+
1210+
// Manually change the description
1211+
const buckinghamPalaceInputEl = getDescription(listContainer, 0)
1212+
1213+
buckinghamPalaceInputEl.value = 'New description'
1214+
buckinghamPalaceInputEl.dispatchEvent(
1215+
new window.Event('change', { bubbles: true })
1216+
)
1217+
buckinghamPalaceInputEl.dispatchEvent(
1218+
new window.KeyboardEvent('keydown', { bubbles: true, code: 'Enter' })
1219+
)
1220+
1221+
expect(JSON.parse(geospatialInput.value)).toEqual([
1222+
{
1223+
...features[0],
1224+
properties: {
1225+
description: 'New description',
1226+
coordinateGridReference: 'TQ 29031 79662',
1227+
centroidGridReference: 'TQ 29031 79662'
1228+
}
1229+
},
1230+
...features.slice(1)
1231+
])
1232+
})
1233+
1234+
test('description enter/return numpad key', () => {
1235+
const { geospatialInput, listContainer } = initialiseGeospatialMaps()
1236+
1237+
// Manually change the description
1238+
const buckinghamPalaceInputEl = getDescription(listContainer, 0)
1239+
1240+
buckinghamPalaceInputEl.value = 'New description'
1241+
buckinghamPalaceInputEl.dispatchEvent(
1242+
new window.Event('change', { bubbles: true })
1243+
)
1244+
buckinghamPalaceInputEl.dispatchEvent(
1245+
new window.KeyboardEvent('keydown', {
1246+
bubbles: true,
1247+
code: 'NumpadEnter'
1248+
})
1249+
)
1250+
1251+
expect(JSON.parse(geospatialInput.value)).toEqual([
1252+
{
1253+
...features[0],
1254+
properties: {
1255+
description: 'New description',
1256+
coordinateGridReference: 'TQ 29031 79662',
1257+
centroidGridReference: 'TQ 29031 79662'
1258+
}
1259+
},
1260+
...features.slice(1)
1261+
])
1262+
})
1263+
1264+
test('createFeaturesHTML - normal', () => {
1265+
const html = createFeaturesHTML(features.slice(1), 'test', false, false)
1266+
1267+
expect(html).toContain('data-action="delete"')
1268+
expect(html).toContain('data-action="edit"')
1269+
expect(html).not.toContain('govuk-link--disabled')
1270+
expect(html).not.toContain('data-action="focus"')
1271+
})
1272+
1273+
test('createFeaturesHTML - normal (defaults)', () => {
1274+
const html = createFeaturesHTML(features.slice(1), 'test')
1275+
1276+
expect(html).toContain('data-action="delete"')
1277+
expect(html).toContain('data-action="edit"')
1278+
expect(html).not.toContain('govuk-link--disabled')
1279+
expect(html).not.toContain('data-action="focus"')
1280+
})
1281+
1282+
test('createFeaturesHTML - readonly (for use in designer viewer)', () => {
1283+
const html = createFeaturesHTML(features.slice(1), 'test', false, true)
1284+
1285+
expect(html).not.toContain('data-action="delete"')
1286+
expect(html).not.toContain('data-action="edit"')
1287+
expect(html).toContain('data-action="focus"')
1288+
})
1289+
1290+
test('createFeaturesHTML - disabled', () => {
1291+
const html = createFeaturesHTML(features.slice(1), 'test', true, false)
1292+
1293+
expect(html).toContain('govuk-link--disabled')
1294+
expect(html).toContain('data-action="delete"')
1295+
expect(html).toContain('data-action="edit"')
1296+
expect(html).not.toContain('data-action="focus"')
1297+
})
1298+
1299+
test('createFeatureHTML - normal (defaults)', () => {
1300+
const html = createFeatureHTML(features[1], 0, 'test')
1301+
1302+
expect(html).toContain('data-action="delete"')
1303+
expect(html).toContain('data-action="edit"')
1304+
expect(html).not.toContain('govuk-link--disabled')
1305+
expect(html).not.toContain('data-action="focus"')
1306+
})
12021307
})
12031308
})
12041309

0 commit comments

Comments
 (0)