Skip to content

Commit

Permalink
WIP switch back to JS unit tests
Browse files Browse the repository at this point in the history
This allows us to get away from testing this custom JavaScript behaviour
in multiple integration tests. We might want to test that everything
works without the add/add and finish buttons being present (it does)
  • Loading branch information
yndajas committed Sep 18, 2024
1 parent 4cb1dae commit f022db3
Show file tree
Hide file tree
Showing 7 changed files with 208 additions and 479 deletions.
68 changes: 34 additions & 34 deletions app/assets/javascripts/modules/accessible-autocomplete.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,48 +3,48 @@
window.GOVUK = window.GOVUK || {}
window.GOVUK.Modules = window.GOVUK.Modules || {}

;(function (Modules) {
'use strict'
; (function (Modules) {
'use strict'

function AccessibleAutocomplete ($module) {
this.$module = $module
}

AccessibleAutocomplete.prototype.init = function () {
const selectElement = this.$module.querySelector('[data-module="accessible-autocomplete"] select')
const configOptions = {
autoselect: true,
defaultValue: '',
preserveNullOptions: true,
selectElement: selectElement,
showAllValues: true
function AccessibleAutocomplete($module) {
this.$module = $module
}

new window.accessibleAutocomplete.enhanceSelectElement(configOptions) // eslint-disable-line no-new, new-cap
AccessibleAutocomplete.prototype.init = function () {
const selectElement = this.$module.querySelector('[data-module="accessible-autocomplete"] select')
const configOptions = {
autoselect: true,
defaultValue: '',
preserveNullOptions: true,
selectElement: selectElement,
showAllValues: true
}

new window.accessibleAutocomplete.enhanceSelectElement(configOptions) // eslint-disable-line no-new, new-cap

const autocompleteElement = selectElement.parentNode.querySelector('.autocomplete__input')
enableArrow(autocompleteElement)
enableAddButton()
resetSelectWhenDesynced(selectElement, autocompleteElement)
enableClearButton(selectElement, autocompleteElement)
}
const autocompleteElement = selectElement.parentNode.querySelector('.autocomplete__input')
enableArrow(this.$module, autocompleteElement)
enableAddButton(this.$module)
resetSelectWhenDesynced(selectElement, autocompleteElement)
enableClearButton(this.$module, selectElement, autocompleteElement)
}

Modules.AccessibleAutocomplete = AccessibleAutocomplete
})(window.GOVUK.Modules)
Modules.AccessibleAutocomplete = AccessibleAutocomplete
})(window.GOVUK.Modules)

function enableArrow (autocompleteElement) {
const arrowElement = autocompleteElement.parentNode.querySelector('.autocomplete__dropdown-arrow-down')
function enableArrow(module, autocompleteElement) {
const arrowElement = module.querySelector('.autocomplete__dropdown-arrow-down')

arrowElement.addEventListener('click', function () {
autocompleteElement.click()
autocompleteElement.focus()
})
}

function enableAddButton () {
const addButton = document.querySelector('.js-autocomplete__add-button')
const addAndFinishButton = document.querySelector('.js-autocomplete__add-and-finish-button')
const addMoreInput = document.querySelector('input[name="application[add_more]"]')
function enableAddButton(module) {
const addButton = module.querySelector('.js-autocomplete__add-button')
const addAndFinishButton = module.querySelector('.js-autocomplete__add-and-finish-button')
const addMoreInput = module.querySelector('input[name="application[add_more]"]')

if (addButton) {
addAndFinishButton.type = 'button'
Expand All @@ -64,12 +64,12 @@ function enableAddButton () {
}
}

function addAndFinish (addMoreInput, addButton) {
function addAndFinish(addMoreInput, addButton) {
addMoreInput.value = 'false'
addButton.click()
}

function resetSelectWhenDesynced (selectElement, autocompleteElement) {
function resetSelectWhenDesynced(selectElement, autocompleteElement) {
// if the autocomplete element's value no longer matches the selected option
// in the select element, reset the select element - in particular, this
// avoids submitting the last selected value after clearing the input
Expand All @@ -84,8 +84,8 @@ function resetSelectWhenDesynced (selectElement, autocompleteElement) {
})
}

function enableClearButton (selectElement, autocompleteElement) {
const clearButton = document.querySelector('.js-autocomplete__clear-button')
function enableClearButton(module, selectElement, autocompleteElement) {
const clearButton = module.querySelector('.js-autocomplete__clear-button')

if (clearButton) {
clearButton.addEventListener('click', function () {
Expand All @@ -102,7 +102,7 @@ function enableClearButton (selectElement, autocompleteElement) {
}
}

function resetSelectAndAutocomplete (selectElement, autocompleteElement, clearButton) {
function resetSelectAndAutocomplete(selectElement, autocompleteElement, clearButton) {
autocompleteElement.value = ''
selectElement.value = ''

Expand Down
53 changes: 27 additions & 26 deletions app/views/shared/_add_permissions_with_autocomplete_form.html.erb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<%= form_tag action, method: :patch do |f| %>
<div data-module="accessible-autocomplete">
<div data-module="accessible-autocomplete">
<%= form_tag action, method: :patch do |f| %>
<%= render "govuk_publishing_components/components/select", {
id: "new_permission_id",
heading_size: "m",
Expand All @@ -8,32 +8,33 @@
name: "application[new_permission_id]",
options: unassigned_permission_options.unshift({ text: '', value: nil })
} %>
</div>

<% assigned_permissions.map(&:id).each do |id| %>
<%= hidden_field_tag "application[current_permission_ids][]", id %>
<% end %>
<div class="govuk-button-group">
<%= render "govuk_publishing_components/components/button", {
text: "Add",
aria_label: "Add permission",
classes: "js-autocomplete__add-button"
} %>
<%= hidden_field_tag "application[add_more]", "false" %>
<%= render "govuk_publishing_components/components/button", {
text: "Add and finish",
aria_label: "Add permission and finish",
classes: "js-autocomplete__add-and-finish-button"
} %>
<div class="govuk-button-group">
<%= render "govuk_publishing_components/components/button", {
text: "Add",
aria_label: "Add permission",
classes: "js-autocomplete__add-button"
} %>
<%= render "govuk_publishing_components/components/button", {
text: "Clear selection",
type: "button",
classes: "js-autocomplete__clear-button",
secondary_solid: true
} %>
</div>

<%= render "govuk_publishing_components/components/button", {
text: "Add and finish",
aria_label: "Add permission and finish",
classes: "js-autocomplete__add-and-finish-button"
} %>

<%= render "govuk_publishing_components/components/button", {
text: "Clear selection",
type: "button",
classes: "js-autocomplete__clear-button",
secondary_solid: true
} %>
</div>
<% end %>
<% assigned_permissions.map(&:id).each do |id| %>
<%= hidden_field_tag "application[current_permission_ids][]", id %>
<% end %>
<%= hidden_field_tag "application[add_more]", "false" %>
<% end %>
</div>
46 changes: 0 additions & 46 deletions spec/javascripts/modules/accessible-autocomplete-dropdown-spec.js

This file was deleted.

93 changes: 93 additions & 0 deletions spec/javascripts/modules/accessible-autocomplete-spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
describe('GOVUK.Modules.AccessibleAutocomplete', function () {
let component, module, autocompleteInput, selectInput

beforeEach(async function () {
component = document.createElement('div')
component.setAttribute('data-module', 'accessible-autocomplete')
component.innerHTML = `
<div class="govuk-form-group gem-c-select">
<label class="govuk-label govuk-label--m" for="new_permission_id">Add a permission</label>
<div id="hint-1234" class="gem-c-hint govuk-hint">
Search for the permission you want to add.
</div>
<select name="application[new_permission_id]" id="new_permission_id" class="govuk-select" aria-describedby="hint-1234">
<option value=""></option>
<option value="1">permission-1</option>
<option value="2">permission-2</option>
<option value="3">permission-3</option>
</select>
</div>
<div class="govuk-button-group">
<button class="gem-c-button govuk-button js-autocomplete__add-button" type="submit" aria-label="Add permission">Add</button>
<button class="gem-c-button govuk-button js-autocomplete__add-and-finish-button" type="submit" aria-label="Add permission and finish">Add and finish</button>
<button class="gem-c-button govuk-button govuk-button--secondary js-autocomplete__clear-button" type="button">Clear selection</button>
</div>
<input type="hidden" name="application[add_more]" id="application_add_more" value="false" autocomplete="off">
`

module = new GOVUK.Modules.AccessibleAutocomplete(component)
module.init()

autocompleteInput = component.querySelector('.autocomplete__input')
selectInput = component.querySelector('select')

autocompleteInput.value = 'per'
await wait()

expect(selectInput.value).toBe('')
const firstAutocompleteListItem = component.querySelector('#new_permission_id__option--0')
firstAutocompleteListItem.click()
expect(autocompleteInput.value).toBe('permission-1')
expect(selectInput.value).toBe('1')
})

it('opens the menu when clicking the arrow', async function () {
const menuElement = component.querySelector('.autocomplete__menu')
const menuElementClassesBefore = Array.from(menuElement.classList)
expect(menuElementClassesBefore.includes('autocomplete__menu--visible')).toBe(false)
expect(menuElementClassesBefore.includes('autocomplete__menu--hidden')).toBe(true)

const arrowElement = component.querySelector('.autocomplete__dropdown-arrow-down')
arrowElement.dispatchEvent(new Event('click'))

await wait()

const menuElementClassesAfter = Array.from(menuElement.classList)
expect(menuElementClassesAfter.includes('autocomplete__menu--visible')).toBe(true)
expect(menuElementClassesAfter.includes('autocomplete__menu--hidden')).toBe(false)
})

it("resets the value of the select element when it no longer matches what's shown in the autocomplete input", async function () {
autocompleteInput.value = 'permission-'
autocompleteInput.dispatchEvent(new KeyboardEvent('keyup'))
await wait()

expect(selectInput.value).toBe('')
})

it('clears the value of the select and autocomplete elements when clicking the clear button', async function () {
const clearButton = component.querySelector('.js-autocomplete__clear-button')
clearButton.click()
await wait()

expect(component.querySelector('select').value).toBe('')
})

it('clears the value of the select and autocomplete elements when hitting space on the clear button', async function () {
const clearButton = component.querySelector('.js-autocomplete__clear-button')
clearButton.dispatchEvent(new KeyboardEvent('keydown', { key: ' ' }))
await wait()

expect(selectInput.value).toBe('')
})

it('clears the value of the select and autocomplete elements when hitting enter on the clear button', async function () {
const clearButton = component.querySelector('.js-autocomplete__clear-button')
clearButton.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }))
await wait()

expect(selectInput.value).toBe('')
})
})

const wait = async () => await new Promise(resolve => setTimeout(resolve, 1000))
Loading

0 comments on commit f022db3

Please sign in to comment.