Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Autocomplete test helpers #248

Merged
merged 1 commit into from
Aug 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/heavy-clocks-collect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@crowdstrike/ember-toucan-core': patch
'@crowdstrike/ember-toucan-form': patch
---

add Autocomplete test helpers.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ Toucan provides a set of accessible and reusable components that make it easy to

The `core` package contains the Toucan-styled Ember components. The `form` package allows users to build forms using [`ember-headless-form`](https://github.com/CrowdStrike/ember-headless-form) with the `core` components.

## Usage
Copy link
Contributor Author

@clintcs clintcs Aug 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bonus. Moved to the top. Happy to revert! But I'm guessing this what most people will be looking for when they visit the repository.


Visit our [documentation website](https://ember-toucan-core.pages.dev/).

### Compatibility

- Ember.js 4.8 or above
Expand Down Expand Up @@ -51,10 +55,6 @@ npm install @crowdstrike/ember-toucan-form
ember install @crowdstrike/ember-toucan-form
```

## Usage

Visit our [documentation website](https://ember-toucan-core.pages.dev/).

## Contributing

See the [Contributing](CONTRIBUTING.md) guide for details.
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,12 @@ export default class ToucanCoreAutocompleteOptionComponent extends Component<Tou
<template>
{{! template-lint-disable require-presentational-children }}
<li
aria-current={{if @isActive "true" "false"}}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaces data-active (below).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh very nice!

aria-selected={{if @isSelected "true" "false"}}
class="my-0 flex cursor-default items-center gap-2 px-2 py-2 leading-4
{{this.styles}}
{{this.className}}
"
data-active={{if @isActive "true" "false"}}
id="{{@popoverId}}-{{@index}}"
role="option"
{{on "click" this.onClick}}
Expand Down
234 changes: 118 additions & 116 deletions packages/ember-toucan-core/src/components/form/controls/autocomplete.gts
Original file line number Diff line number Diff line change
Expand Up @@ -468,123 +468,125 @@ export default class ToucanFormAutocompleteControlComponent extends Component<To
@placement="bottom-start"
as |velcro|
>
<div class="relative flex w-full items-center">
{{! template-lint-disable no-redundant-role }}
<input
aria-activedescendant={{this.activeDescendant}}
aria-autocomplete="list"
aria-controls={{this.popoverId}}
aria-expanded={{this.isPopoverOpen}}
aria-haspopup="listbox"
autocapitalize="none"
autocomplete="off"
autocorrect="off"
class="focus:outline-none w-full rounded-sm border-none py-1 pl-2 pr-6 transition-shadow
{{this.styles}}"
disabled={{@isDisabled}}
readonly={{@isReadOnly}}
role="combobox"
spellcheck="false"
type="text"
value={{this.inputValue}}
{{on
"blur"
(if
this.isDisabledOrReadOnlyOrWithoutOptions
this.noop
this.closePopover
)
}}
{{on
"blur"
(if
this.isDisabledOrReadOnlyOrWithoutOptions
this.noop
this.resetEverything
)
}}
{{on
"click"
(if
this.isDisabledOrReadOnlyOrWithoutOptions
this.noop
this.openPopover
)
}}
{{on
"click"
(if
this.isDisabledOrReadOnlyOrWithoutOptions
this.noop
this.highlightInputValue
)
}}
{{on
"focus"
(if
this.isDisabledOrReadOnlyOrWithoutOptions
this.noop
this.openPopover
)
}}
{{on
"keydown"
(if
this.isDisabledOrReadOnlyOrWithoutOptions this.noop this.onKeydown
)
}}
{{on
"input"
(if
this.isDisabledOrReadOnlyOrWithoutOptions this.noop this.onInput
)
}}
{{velcro.hook}}
...attributes
/>

<this.Chevron
class="text-text-and-icons pointer-events-none absolute right-1 top-0 ml-auto h-full transform transition-transform duration-200
{{if this.isPopoverOpen 'rotate-180'}}"
/>
</div>

{{#if this.isPopoverOpen}}
<ul
class="border-surface-inner bg-surface-2xl max-h-listbox my-0 list-none overflow-y-auto overscroll-contain rounded-sm border py-1 pl-0 shadow-xl
{{if @contentClass @contentClass}}"
id={{this.popoverId}}
role="listbox"
{{velcro.loop}}
>
{{#if this.options}}
{{#each this.options as |option index|}}
{{yield
(hash
Option=(component
this.Option
isActive=(this.isEqual index this.activeIndex)
isDisabled=@isDisabled
isSelected=(this.isEqual @selected option)
isReadOnly=@isReadOnly
onClick=(fn this.onChange index)
onMouseover=(fn this.onOptionMouseover index)
popoverId=this.popoverId
index=index
<div data-autocomplete>
<div class="relative flex w-full items-center">
{{! template-lint-disable no-redundant-role }}
<input
aria-activedescendant={{this.activeDescendant}}
aria-autocomplete="list"
aria-controls={{this.popoverId}}
aria-expanded={{this.isPopoverOpen}}
aria-haspopup="listbox"
autocapitalize="none"
autocomplete="off"
autocorrect="off"
class="focus:outline-none w-full rounded-sm border-none py-1 pl-2 pr-6 transition-shadow
{{this.styles}}"
disabled={{@isDisabled}}
readonly={{@isReadOnly}}
role="combobox"
spellcheck="false"
type="text"
value={{this.inputValue}}
{{on
"blur"
(if
this.isDisabledOrReadOnlyOrWithoutOptions
this.noop
this.closePopover
)
}}
{{on
"blur"
(if
this.isDisabledOrReadOnlyOrWithoutOptions
this.noop
this.resetEverything
)
}}
{{on
"click"
(if
this.isDisabledOrReadOnlyOrWithoutOptions
this.noop
this.openPopover
)
}}
{{on
"click"
(if
this.isDisabledOrReadOnlyOrWithoutOptions
this.noop
this.highlightInputValue
)
}}
{{on
"focus"
(if
this.isDisabledOrReadOnlyOrWithoutOptions
this.noop
this.openPopover
)
}}
{{on
"keydown"
(if
this.isDisabledOrReadOnlyOrWithoutOptions this.noop this.onKeydown
)
}}
{{on
"input"
(if
this.isDisabledOrReadOnlyOrWithoutOptions this.noop this.onInput
)
}}
{{velcro.hook}}
...attributes
/>

<this.Chevron
class="text-text-and-icons pointer-events-none absolute right-1 top-0 ml-auto h-full transform transition-transform duration-200
{{if this.isPopoverOpen 'rotate-180'}}"
/>
</div>

{{#if this.isPopoverOpen}}
<ul
class="border-surface-inner bg-surface-2xl max-h-listbox my-0 list-none overflow-y-auto overscroll-contain rounded-sm border py-1 pl-0 shadow-xl
{{if @contentClass @contentClass}}"
id={{this.popoverId}}
role="listbox"
{{velcro.loop}}
>
{{#if this.options}}
{{#each this.options as |option index|}}
{{yield
(hash
Option=(component
this.Option
isActive=(this.isEqual index this.activeIndex)
isDisabled=@isDisabled
isSelected=(this.isEqual @selected option)
isReadOnly=@isReadOnly
onClick=(fn this.onChange index)
onMouseover=(fn this.onOptionMouseover index)
popoverId=this.popoverId
index=index
)
option=option
)
option=option
)
}}
{{/each}}
{{else}}
<li
aria-live="assertive"
class="text-titles-and-attributes my-0 flex cursor-default items-center px-3 py-2 leading-4"
role="status"
>{{@noResultsText}}</li>
{{/if}}
</ul>
{{/if}}
}}
{{/each}}
{{else}}
<li
aria-live="assertive"
class="text-titles-and-attributes my-0 flex cursor-default items-center px-3 py-2 leading-4"
role="status"
>{{@noResultsText}}</li>
{{/if}}
</ul>
{{/if}}
</div>
</Velcro>
</template>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { PageObject } from 'fractal-page-object';

export class AutocompletePageObject extends PageObject {
get active() {
return this.element
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We spread attributes on the <input />, which seems right to me. However, it means we need to first get ahold of the parent element (#autocomplete) before we can select children.

?.closest('[data-autocomplete]')
?.querySelector('[aria-current="true"]');
}

get list() {
return this.element
?.closest('[data-autocomplete]')
?.querySelector('[role="listbox"]');
}

get options() {
return this.element
?.closest('[data-autocomplete]')
?.querySelectorAll('[role="option"]');
}

get selected() {
return this.element
?.closest('[data-autocomplete]')
?.querySelector('[aria-selected="true"]');
}

get status() {
return this.element
?.closest('[data-autocomplete]')
?.querySelector('[role="status"]');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { click } from '@ember/test-helpers';

import { PageObject } from 'fractal-page-object';

export class Button extends PageObject {
export class ButtonPageObject extends PageObject {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Due to an export name change, should we consider a breaking-change changeset? I'd be okay making it a patch, as I'm not sure anyone is using this directly. As long as it's documented in the release notes 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm cool with that.

Though I tend to wait to add a changeset until after PR review in case there's some discussion. I'll make sure to add one after approval.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

async click() {
if (this.element) {
await click(this.element);
Expand Down
3 changes: 2 additions & 1 deletion packages/ember-toucan-core/src/test-support/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { Button } from './components/button';
export { AutocompletePageObject } from './components/autocomplete';
export { ButtonPageObject } from './components/button';
Loading