Skip to content

Commit

Permalink
Switch back to Jasmine unit tests
Browse files Browse the repository at this point in the history
When improving the clear button for the accessible autocomplete
component (0c5d5a1), we moved to integration tests using Capybara with
Selenium, instead of unit tests with Jasmine. This was because the
button was moved outside of the autocomplete module

This slightly restructures the template where we use accessible
autocomplete to wrap all the elements the JavaScript needs to access in
the `div[data-module=accessible-autocomplete]` element. We then use
dependency injection to pass the module to custom functions that enhance
the form. These changes allow us to revert to the unit test approach,
which in turn means we can introduce the autocomplete component into
other templates without needing to test context-independent
functionality in multiple integration test specs

I've retained a couple of the JavaScript-enabled integration tests where
they're more about checking everything works in a specific context
  • Loading branch information
yndajas committed Sep 19, 2024
1 parent 9256fb4 commit 37b2f1e
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 152 deletions.
22 changes: 11 additions & 11 deletions app/assets/javascripts/modules/accessible-autocomplete.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,28 +23,28 @@ window.GOVUK.Modules = window.GOVUK.Modules || {}
new window.accessibleAutocomplete.enhanceSelectElement(configOptions) // eslint-disable-line no-new, new-cap

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

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 Down Expand Up @@ -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 Down
52 changes: 26 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,32 @@
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 %>
<% assigned_permissions.map(&:id).each do |id| %>
<%= hidden_field_tag "application[current_permission_ids][]", id %>
<% end %>
<%= hidden_field_tag "application[add_more]", "false" %>
<%= hidden_field_tag "application[add_more]", "false" %>

<div class="govuk-button-group">
<%= render "govuk_publishing_components/components/button", {
text: "Add",
aria_label: "Add permission",
classes: "js-autocomplete__add-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: "Add and finish",
aria_label: "Add permission and finish",
classes: "js-autocomplete__add-and-finish-button"
} %>
<%= 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 %>
<%= render "govuk_publishing_components/components/button", {
text: "Clear selection",
type: "button",
classes: "js-autocomplete__clear-button",
secondary_solid: true
} %>
</div>
<% end %>
</div>
46 changes: 0 additions & 46 deletions spec/javascripts/modules/accessible-autocomplete-dropdown-spec.js

This file was deleted.

95 changes: 95 additions & 0 deletions spec/javascripts/modules/accessible-autocomplete-spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
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 = `
<form action="/whatever" method="post">
<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>
<input type="hidden" name="application[add_more]" id="application_add_more" value="false" autocomplete="off">
<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>
</form>
`

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, 100))
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,6 @@ def assert_select_permission_to_grant_with_javascript
assert_equal @new_permission_to_grant.id.to_s, @select_element.value
end

def assert_permissions_unchanged
expected_permissions = [*@old_permissions_to_keep, @old_permission_to_remove_without_javascript]
expected_permissions.each { |expected_permission| assert @grantee.has_permission?(expected_permission) }

unexpected_permissions = [*@new_permissions_to_leave, @new_permission_to_grant]
unexpected_permissions.each { |unexpected_permission| assert_not @grantee.has_permission?(unexpected_permission) }
end

context "with apps that have more than eight permissions" do
context "with JavaScript disabled" do
setup { shared_setup }
Expand Down Expand Up @@ -158,67 +150,6 @@ def assert_permissions_unchanged
assert page.has_selector?("h1", text: h1_content)
assert_flash_content("You have successfully added the permission '#{@new_permission_to_grant.name}'.")
end

should "reset the value of the select element when it no longer matches what's shown in the autocomplete input" do
assert_select_permission_to_grant_with_javascript

@autocomplete_input.fill_in with: "addin"
autocomplete_option = find(".autocomplete__option")

assert_equal "addin", @autocomplete_input.value
assert_equal @new_permission_to_grant.name, autocomplete_option.text
assert_equal "", @select_element.value

@autocomplete_input.native.send_keys :escape
click_button "Add and finish"

assert_permissions_unchanged
assert_flash_content("You must select a permission.")
end

should "clear the value of the select and autocomplete elements when clicking the clear button" do
assert_select_permission_to_grant_with_javascript

click_button "Clear selection"

assert_equal "", @autocomplete_input.value
assert_equal "", @select_element.value

click_button "Add and finish"

assert_permissions_unchanged
assert_flash_content("You must select a permission.")
end

should "clear the value of the select and autocomplete elements when hitting space on the clear button" do
assert_select_permission_to_grant_with_javascript

clear_button = find(".js-autocomplete__clear-button")
clear_button.native.send_keys :space

assert_equal "", @autocomplete_input.value
assert_equal "", @select_element.value

click_button "Add and finish"

assert_permissions_unchanged
assert_flash_content("You must select a permission.")
end

should "clear the value of the select and autocomplete elements when hitting enter on the clear button" do
assert_select_permission_to_grant_with_javascript

clear_button = find(".js-autocomplete__clear-button")
clear_button.native.send_keys :enter

assert_equal "", @autocomplete_input.value
assert_equal "", @select_element.value

click_button "Add and finish"

assert_permissions_unchanged
assert_flash_content("You must select a permission.")
end
end
end
end

0 comments on commit 37b2f1e

Please sign in to comment.