Skip to content

Commit 7e3acd6

Browse files
committed
Add basic javascript specs for combobox
1 parent 715b115 commit 7e3acd6

File tree

5 files changed

+116
-2
lines changed

5 files changed

+116
-2
lines changed

app/javascript/controllers/searches/combobox.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ export default class extends Controller {
8383
}
8484

8585
tryOpen() {
86+
console.log('Trying to open');
8687
if (this.options.length > 0) {
8788
this.open();
8889
} else {
@@ -95,6 +96,7 @@ export default class extends Controller {
9596
}
9697

9798
expand() {
99+
console.log('Expand');
98100
this.listbox.classList.remove('hidden');
99101
this.combobox.setAttribute('aria-expanded', true);
100102
}
@@ -120,6 +122,7 @@ export default class extends Controller {
120122
}
121123

122124
collapse() {
125+
console.log('Collapse');
123126
this.combobox.setAttribute('aria-expanded', false);
124127
this.listbox.classList.add('hidden');
125128
}
@@ -151,6 +154,7 @@ export default class extends Controller {
151154
}
152155

153156
navigate(event) {
157+
console.log('Navigating', event); // event.key, e.g. "ArrowDown"
154158
this.navigationKeyHandlers[event.key]?.call(this, event);
155159
}
156160

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import {
2+
describe,
3+
it,
4+
expect,
5+
render,
6+
startStimulus,
7+
screen,
8+
userEvent,
9+
beforeEach,
10+
} from '../../test/utils';
11+
12+
import SearchCombobox from './combobox';
13+
14+
import html from '../../test/fixtures/views/searches/combobox.html?raw';
15+
16+
describe('Combobox', () => {
17+
beforeEach(() => {
18+
startStimulus('search-combobox', SearchCombobox);
19+
});
20+
21+
it('should open the combobox', async () => {
22+
const user = userEvent.setup();
23+
24+
await render(html);
25+
26+
const combobox = await screen.findByRole('combobox');
27+
const listbox = await screen.findByRole('listbox');
28+
29+
expect(combobox).toHaveAttribute('aria-expanded');
30+
31+
user.click(combobox);
32+
33+
expect(combobox).toHaveAttribute('aria-expanded', 'true');
34+
expect(listbox).not.toHaveClass('hidden');
35+
expect(listbox).toBeVisible();
36+
});
37+
38+
it('should close the combobox', async () => {
39+
const user = userEvent.setup();
40+
41+
await render(html);
42+
43+
const combobox = await screen.findByRole('combobox');
44+
const listbox = await screen.findByRole('listbox');
45+
46+
await user.click(combobox);
47+
await user.keyboard('[Escape]');
48+
49+
expect(combobox).toHaveAttribute('aria-expanded', 'false');
50+
expect(listbox).toHaveClass('hidden');
51+
});
52+
53+
it('should select option', async () => {
54+
const user = userEvent.setup();
55+
56+
await render(html);
57+
58+
const combobox = await screen.findByRole('combobox');
59+
60+
const option = await screen.findByRole('option', {
61+
name: 'Introducing Joy of Rails',
62+
});
63+
64+
await user.click(combobox);
65+
66+
// Select second option
67+
await user.keyboard('[ArrowDown]');
68+
await user.keyboard('[ArrowDown]');
69+
70+
expect(combobox).toHaveAttribute('aria-expanded', 'true');
71+
expect(combobox).toHaveAttribute('aria-activedescendant', option.id);
72+
73+
expect(option).toHaveClass('selected');
74+
expect(option).toHaveAttribute('aria-selected', 'true');
75+
});
76+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<div class="combobox grid gap-2" data-controller="search-combobox" data-action="
2+
keydown->search-combobox#navigate
3+
search-listbox:connected->search-combobox#tryOpen
4+
dialog:close@window->search-combobox#closeInTarget
5+
dialog:open@window->search-combobox#openInTarget
6+
"><form data-controller="autosubmit-form" data-autosubmit-delay-value="300" data-autosubmit-minimum-length-value="3" data-turbo-frame="search" action="/search" accept-charset="UTF-8" method="post"><div class="flex items-center flex-row pl-2 col-gap-xs"><svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 1200 1200" class="w-[32px] fill-current text-theme">
7+
<path d="m1135.2 986.4-254.4-226.8c124.8-181.2 105.6-430.8-55.199-591.6-181.2-182.4-476.4-182.4-657.6 0-182.4 181.2-182.4 476.4 0 658.8 160.8 160.8 410.4 178.8 591.6 55.199l226.8 253.2c38.398 43.199 105.6 45.602 146.4 3.6016l6-6c40.801-40.801 39.598-108-3.6016-146.4zm-422.4-273.6c-120 120-313.2 120-432 0-120-120-120-313.2 0-432 120-120 313.2-120 432 0 120 120 120 313.2 0 432z"></path>
8+
</svg><label for="query" class="sr-only">Search</label> <input value="Rails" autofocus="autofocus" role="combobox" aria-expanded="false" aria-autocomplete="none" aria-controls="search-listbox" aria-owns="search-listbox" aria-haspopup="listbox" aria-activedescendant="" data-action="
9+
focus-&gt;combobox#tryOpen
10+
input-&gt;autosubmit-form#submit
11+
" placeholder="Search Joy of Rails" class="w-full step-1" type="search" name="query" id="query" /></div></form><ul id="search-listbox" role="listbox" class="grid" data-controller="search-listbox"><li aria-label="Mastering Custom Configuration in Rails" role="option" id="search-option_page_01jc3etwr9rb4gabrneh0dwjjh" class="rounded"><a href="/articles/mastering-custom-configuration-in-rails" data-turbo-frame="_top" class="p-2 block"><div class="font-semibold">Mastering Custom Configuration in <mark>Rails</mark></div><div class="text-sm">As your Ruby on <mark>Rails</mark> application grows, you‘ll need to add your own bits of configuration. Where do you put API keys for third-party apps? What if you need different…</div></a></li><li aria-label="Introducing Joy of Rails" role="option" id="search-option_page_01jc3etwj4p7csjx5a92qxpzad" class="rounded"><a href="/articles/introducing-joy-of-rails" data-turbo-frame="_top" class="p-2 block"><div class="font-semibold">Introducing Joy of <mark>Rails</mark></div><div class="text-sm">…With Joy of <mark>Rails</mark>, I want to do something a little different—more than I can with a static website. Built with <mark>Rails</mark> Joy of <mark>Rails</mark> is a <mark>Rails</mark> monolith currently running…</div></a></li><li aria-label="Add your Rails app to the Home Screen - the Ultimate Guide" role="option" id="search-option_page_01jc3etwn1nrym36ch59gf03gy" class="rounded"><a href="/articles/add-your-rails-app-to-the-home-screen" data-turbo-frame="_top" class="p-2 block"><div class="font-semibold">Add your <mark>Rails</mark> app to the Home Screen - the Ultimate Guide</div><div class="text-sm">…In this article, I‘ll show you how to set up your <mark>Rails</mark> app to be installable as a PWA. Install Joy of <mark>Rails</mark> Since Joy of <mark>Rails</mark> is itself a <mark>Rails</mark></div></a></li></ul></div>

app/views/searches/listbox.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,12 @@ def view_template
2020
)) do
2121
if pages.any?
2222
pages.each.with_index do |page, i|
23-
li(role: "option", id: dom_id(page, "search-option"), class: "rounded") do
23+
li(
24+
aria: {label: page.title},
25+
role: "option",
26+
id: dom_id(page, "search-option"),
27+
class: "rounded"
28+
) do
2429
a(
2530
href: page.request_path,
2631
data: {

lib/tasks/fixtures/html.rake

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,25 @@ namespace :fixtures do
88
end
99
end
1010

11-
File.write(Rails.root.join("app", "javascript", "test", "fixtures", "views", "darkmode", "switch.html"), DarkMode::Switch.call)
11+
Rake::Task["db:seed"].invoke
12+
13+
request = ActionDispatch::Request.new({})
14+
request.routes = ApplicationController._routes
15+
16+
instance = ApplicationController.new
17+
instance.set_request! request
18+
instance.set_response! ApplicationController.make_response!(request)
19+
view_context = instance.view_context
20+
21+
File.write(
22+
Rails.root.join("app", "javascript", "test", "fixtures", "views", "darkmode", "switch.html"),
23+
DarkMode::Switch.call
24+
)
25+
26+
File.write(
27+
Rails.root.join("app", "javascript", "test", "fixtures", "views", "searches", "combobox.html"),
28+
Searches::Combobox.new(pages: Page.search("Rails*").with_snippets.ranked.limit(3), query: "Rails").call(view_context:)
29+
)
1230
end
1331
end
1432

0 commit comments

Comments
 (0)