Skip to content

Commit 5d67687

Browse files
committed
Adapt mention-listbox and update tests to wait for positioning
1 parent b39f755 commit 5d67687

File tree

7 files changed

+55
-26
lines changed

7 files changed

+55
-26
lines changed

packages/nimble-components/src/combobox/testing/combobox.pageobject.ts

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import type { Combobox } from '..';
33
import { listOptionTag } from '../../list-option';
44
import { waitForUpdatesAsync } from '../../testing/async-helpers';
55
import {
6-
waitForEvent,
7-
waitAnimationFrame
6+
waitAnimationFrame,
7+
waitPredicate
88
} from '../../utilities/testing/component';
99
import { slotTextContent } from '../../utilities/models/slot-text-content';
1010

@@ -13,11 +13,6 @@ import { slotTextContent } from '../../utilities/models/slot-text-content';
1313
* of querying and interacting with the component during tests.
1414
*/
1515
export class ComboboxPageObject {
16-
private readonly regionLoadedListener = waitForEvent(
17-
this.comboboxElement,
18-
'loaded'
19-
);
20-
2116
public constructor(protected readonly comboboxElement: Combobox) {}
2217

2318
/**
@@ -31,7 +26,7 @@ export class ComboboxPageObject {
3126
* Sets the input text and commits the value by pressing Enter. Will emit a 'change' event.
3227
*/
3328
public async commitValue(text: string): Promise<void> {
34-
await this.waitForAnchoredRegionLoaded();
29+
await this.waitForListboxPositioned();
3530
this.setInputText(text);
3631
this.pressEnterKey();
3732
}
@@ -76,7 +71,7 @@ export class ComboboxPageObject {
7671
*/
7772
public async clickAndWaitForOpen(): Promise<void> {
7873
this.clickCombobox();
79-
await this.waitForAnchoredRegionLoaded();
74+
await this.waitForListboxPositioned();
8075
await waitForUpdatesAsync();
8176
await waitAnimationFrame(); // necessary because scrolling is queued with requestAnimationFrame
8277
}
@@ -95,7 +90,7 @@ export class ComboboxPageObject {
9590
*/
9691
public async clickDropdownButtonAndWaitForOpen(): Promise<void> {
9792
this.clickDropdownButton();
98-
await this.waitForAnchoredRegionLoaded();
93+
await this.waitForListboxPositioned();
9994
await waitAnimationFrame(); // necessary because scrolling is queued with requestAnimationFrame
10095
}
10196

@@ -219,7 +214,14 @@ export class ComboboxPageObject {
219214
);
220215
}
221216

222-
private async waitForAnchoredRegionLoaded(): Promise<void> {
223-
await this.regionLoadedListener;
217+
/**
218+
* @internal
219+
*/
220+
public async waitForListboxPositioned(): Promise<void> {
221+
await waitPredicate(
222+
() => this.comboboxElement.region?.classList.contains(
223+
'horizontal-center'
224+
) === true
225+
);
224226
}
225227
}

packages/nimble-components/src/combobox/tests/combobox.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -508,7 +508,7 @@ describe('Combobox', () => {
508508
it('should set classes based on open, disabled, and position', async () => {
509509
const { element, connect, disconnect } = await setup();
510510
await connect();
511-
await waitForUpdatesAsync();
511+
await new ComboboxPageObject(element).waitForListboxPositioned();
512512

513513
expect(element.classList.contains('open')).toBeTrue();
514514
expect(element.classList.contains('disabled')).toBeTrue();

packages/nimble-components/src/rich-text/mention-listbox/index.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export class RichTextMentionListbox extends FoundationListbox {
4040
* @internal
4141
*/
4242
@observable
43-
public position?: DropdownPosition;
43+
public availableViewportHeight = 0;
4444

4545
/**
4646
* @internal
@@ -107,6 +107,7 @@ export class RichTextMentionListbox extends FoundationListbox {
107107
* @public
108108
*/
109109
public show(options: MentionListboxShowOptions): void {
110+
this.updateAvailableViewportHeight();
110111
this.filter = options.filter;
111112
this.anchorElement = options.anchorNode;
112113
this.setOpen(true);
@@ -282,6 +283,19 @@ export class RichTextMentionListbox extends FoundationListbox {
282283
private setOpen(value: boolean): void {
283284
this.open = value;
284285
}
286+
287+
private updateAvailableViewportHeight(): void {
288+
const currentBox = this.getBoundingClientRect();
289+
const viewportHeight = document.documentElement.getBoundingClientRect().height;
290+
const availableSpaceAbove = Math.trunc(currentBox.top);
291+
const availableSpaceBelow = Math.trunc(
292+
viewportHeight - currentBox.bottom
293+
);
294+
this.availableViewportHeight = Math.max(
295+
availableSpaceAbove,
296+
availableSpaceBelow
297+
);
298+
}
285299
}
286300

287301
const nimbleRichTextMentionListbox = RichTextMentionListbox.compose({

packages/nimble-components/src/rich-text/mention-listbox/template.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,18 @@ import { Listbox } from '@ni/fast-foundation';
33
import type { RichTextMentionListbox } from '.';
44
import { anchoredRegionTag } from '../../anchored-region';
55
import { filterNoResultsLabel } from '../../label-provider/core/label-tokens';
6-
import { DropdownPosition } from '../../patterns/dropdown/types';
76

87
/* eslint-disable @typescript-eslint/indent */
98
// prettier-ignore
109
export const template = html<RichTextMentionListbox>`
1110
<template>
1211
<${anchoredRegionTag}
1312
${ref('region')}
14-
class="anchored-region"
13+
class="anchored-region confined-to-view"
1514
fixed-placement
1615
auto-update-mode="auto"
17-
vertical-default-position="${x => (x.position === DropdownPosition.above ? 'top' : 'bottom')}"
18-
vertical-positioning-mode="${x => (!x.position ? 'dynamic' : 'locktodefault')}"
16+
vertical-default-position="bottom"
17+
vertical-positioning-mode="dynamic"
1918
horizontal-default-position="center"
2019
horizontal-positioning-mode="locktodefault"
2120
horizontal-scaling="anchor"
@@ -30,6 +29,7 @@ export const template = html<RichTextMentionListbox>`
3029
role="listbox"
3130
@click="${(x, c) => x.clickHandler(c.event as MouseEvent)}"
3231
?disabled="${x => x.disabled}"
32+
style="--ni-private-listbox-available-viewport-height: ${x => x.availableViewportHeight}px;"
3333
${ref('listbox')}
3434
>
3535
<slot name="option"

packages/nimble-components/src/rich-text/mention-listbox/tests/mention-listbox.spec.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import { waitForUpdatesAsync } from '../../../testing/async-helpers';
44
import { type Fixture, fixture } from '../../../utilities/tests/fixture';
55
import { listOptionTag } from '../../../list-option';
66
import {
7-
waitForEvent,
8-
waitAnimationFrame
7+
waitAnimationFrame,
8+
waitPredicate
99
} from '../../../utilities/testing/component';
1010
import { checkFullyInViewport } from '../../../utilities/tests/intersection-observer';
1111

@@ -49,12 +49,13 @@ describe('RichTextMentionListbox', () => {
4949
}
5050

5151
async function showAndWaitForOpen(filter = ''): Promise<void> {
52-
const regionLoadedPromise = waitForEvent(element, 'loaded');
5352
element.show({
5453
anchorNode: anchor,
5554
filter
5655
});
57-
await regionLoadedPromise;
56+
await waitPredicate(
57+
() => element.region?.classList.contains('horizontal-center') === true
58+
);
5859
}
5960

6061
beforeEach(async () => {

packages/nimble-components/src/select/tests/select.spec.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import { checkFullyInViewport } from '../../utilities/tests/intersection-observe
1212
import { FilterMode, type SelectFilterInputEventDetail } from '../types';
1313
import {
1414
waitForEvent,
15-
waitAnimationFrame
15+
waitAnimationFrame,
16+
waitPredicate
1617
} from '../../utilities/testing/component';
1718
import { filterSearchLabel } from '../../label-provider/core/label-tokens';
1819
import { ListOptionGroup, listOptionGroupTag } from '../../list-option-group';
@@ -107,9 +108,11 @@ async function setupWithGroups(): Promise<Fixture<Select>> {
107108
}
108109

109110
async function clickAndWaitForOpen(select: Select): Promise<void> {
110-
const regionLoadedPromise = waitForEvent(select, 'loaded');
111111
select.click();
112-
await regionLoadedPromise;
112+
await waitPredicate(
113+
() => select.anchoredRegion?.classList.contains('horizontal-center')
114+
=== true
115+
);
113116
}
114117

115118
describe('Select', () => {
@@ -939,7 +942,6 @@ describe('Select', () => {
939942
await connect();
940943
element.focus();
941944
await clickAndWaitForOpen(element);
942-
await waitForUpdatesAsync();
943945
await waitAnimationFrame(); // necessary because scrolling is queued with requestAnimationFrame
944946

945947
expect(element.scrollableRegion.scrollTop).toBeGreaterThan(8000);

packages/nimble-components/src/utilities/testing/component.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,13 @@ export async function waitTimeout(ms = 0): Promise<void> {
7979
window.setTimeout(() => resolve(undefined), ms);
8080
});
8181
}
82+
83+
/**
84+
* Waits for a predicate function to return truthy value (polls in loop of waitForUpdatesAsync calls).
85+
*/
86+
export async function waitPredicate(predicate: () => boolean): Promise<void> {
87+
while (!predicate()) {
88+
// eslint-disable-next-line no-await-in-loop
89+
await waitForUpdatesAsync();
90+
}
91+
}

0 commit comments

Comments
 (0)