diff --git a/packages/nimble-components/src/rich-text/editor/testing/rich-text-editor.pageobject.ts b/packages/nimble-components/src/rich-text/editor/testing/rich-text-editor.pageobject.ts index 1863bd4b87..484c2a206d 100644 --- a/packages/nimble-components/src/rich-text/editor/testing/rich-text-editor.pageobject.ts +++ b/packages/nimble-components/src/rich-text/editor/testing/rich-text-editor.pageobject.ts @@ -248,7 +248,7 @@ export class RichTextEditorPageObject { .run(); await waitForUpdatesAsync(); - if (this.isMentionListboxOpened()) { + if (await this.isMentionListboxOpened()) { this.richTextEditorElement.tiptapEditor.commands.focus(); await waitForUpdatesAsync(); } @@ -274,18 +274,47 @@ export class RichTextEditorPageObject { .deleteRange({ from, to }) .run(); await waitForUpdatesAsync(); + + if (await this.isMentionListboxOpened()) { + this.richTextEditorElement.tiptapEditor.commands.focus(); + await waitForUpdatesAsync(); + } } public getEditorLastChildAttribute(attribute: string): string { return getLastChildElementAttribute(attribute, this.getTiptapEditor()); } - public isMentionListboxOpened(): boolean { - return ( - !this.getMentionListbox() - ?.shadowRoot?.querySelector(anchoredRegionTag) - ?.hasAttribute('hidden') ?? false + public async isMentionListboxOpened(): Promise { + const mentionListbox = this.getMentionListbox(); + if (!mentionListbox) { + // eslint-disable-next-line no-console + console.log('Mention listbox not found'); + return false; + } + const shadowRoot = mentionListbox.shadowRoot; + if (!shadowRoot) { + // eslint-disable-next-line no-console + console.log('Mention listbox shadow root not found'); + return false; + } + + const anchoredRegion = shadowRoot.querySelector(anchoredRegionTag); + if (!anchoredRegion) { + // eslint-disable-next-line no-console + console.log('Anchored region not found'); + return false; + } + await waitForUpdatesAsync(); + + // eslint-disable-next-line no-console + console.log( + 'Anchored region hidden attribute:', + !anchoredRegion.hasAttribute('hidden') ); + // eslint-disable-next-line no-console + console.log('Anchored region hidden property:', !anchoredRegion.hidden); + return !anchoredRegion.hidden; } public getEditorMentionViewAttributeValues(attribute: string): string[] { @@ -439,7 +468,16 @@ export class RichTextEditorPageObject { } public async clickMentionListboxOption(index: number): Promise { + // if (await this.isMentionListboxOpened()) { + // this.richTextEditorElement.tiptapEditor.commands.focus(); + // await waitForUpdatesAsync(); + // } const listOption = this.getAllListItemsInMentionBox()[index]; + // eslint-disable-next-line no-console + console.log( + 'Editor focus status:', + this.richTextEditorElement.tiptapEditor.isFocused + ); listOption?.click(); await waitForUpdatesAsync(); } diff --git a/packages/nimble-components/src/rich-text/editor/tests/rich-text-editor-mention.spec.ts b/packages/nimble-components/src/rich-text/editor/tests/rich-text-editor-mention.spec.ts index 86638f51e7..729ff9fc56 100644 --- a/packages/nimble-components/src/rich-text/editor/tests/rich-text-editor-mention.spec.ts +++ b/packages/nimble-components/src/rich-text/editor/tests/rich-text-editor-mention.spec.ts @@ -1,3 +1,5 @@ +/* eslint-disable no-useless-concat */ +/* eslint-disable @typescript-eslint/no-loop-func */ import { html } from '@microsoft/fast-element'; import { parameterizeSpec } from '@ni/jasmine-parameterized'; import { richTextEditorTag, RichTextEditor } from '..'; @@ -1067,7 +1069,7 @@ describe('RichTextEditor user mention via template', () => { it('should open mention popup, when button clicked', async () => { await pageObject.clickUserMentionButton(); - expect(pageObject.isMentionListboxOpened()).toBeTrue(); + expect(await pageObject.isMentionListboxOpened()).toBeTrue(); }); }); @@ -1104,9 +1106,9 @@ describe('RichTextEditorMentionListbox', () => { { key: 'user:1', displayName: 'username1' }, { key: 'user:2', displayName: 'username2' } ]); - expect(pageObject.isMentionListboxOpened()).toBeFalse(); + expect(await pageObject.isMentionListboxOpened()).toBeFalse(); await pageObject.setEditorTextContent('@'); - expect(pageObject.isMentionListboxOpened()).toBeTrue(); + expect(await pageObject.isMentionListboxOpened()).toBeTrue(); expect(pageObject.getMentionListboxItemsName()).toEqual([ 'username1', 'username2' @@ -1143,9 +1145,9 @@ describe('RichTextEditorMentionListbox', () => { { key: 'user:1', displayName: 'username1' }, { key: 'user:2', displayName: 'username2' } ]); - expect(pageObject.isMentionListboxOpened()).toBeFalse(); + expect(await pageObject.isMentionListboxOpened()).toBeFalse(); await pageObject.setEditorTextContent('@'); - expect(pageObject.isMentionListboxOpened()).toBeTrue(); + expect(await pageObject.isMentionListboxOpened()).toBeTrue(); expect(pageObject.getMentionListboxItemsName()).toEqual([ 'username1', 'username2' @@ -1177,9 +1179,9 @@ describe('RichTextEditorMentionListbox', () => { await appendUserMentionConfiguration(element, [ { key: 'user:1', displayName: 'username1' } ]); - expect(pageObject.isMentionListboxOpened()).toBeFalse(); + expect(await pageObject.isMentionListboxOpened()).toBeFalse(); await pageObject.setEditorTextContent('@'); - expect(pageObject.isMentionListboxOpened()).toBeTrue(); + expect(await pageObject.isMentionListboxOpened()).toBeTrue(); await pageObject.clickMentionListboxOption(0); expect(pageObject.getMarkdownRenderedTagNames()).toEqual([ @@ -1189,16 +1191,16 @@ describe('RichTextEditorMentionListbox', () => { expect( pageObject.getEditorMentionViewAttributeValues('mention-label') ).toEqual(['username1']); - expect(pageObject.isMentionListboxOpened()).toBeFalse(); + expect(await pageObject.isMentionListboxOpened()).toBeFalse(); }); it('should commit mention into the editor on Enter', async () => { await appendUserMentionConfiguration(element, [ { key: 'user:1', displayName: 'username1' } ]); - expect(pageObject.isMentionListboxOpened()).toBeFalse(); + expect(await pageObject.isMentionListboxOpened()).toBeFalse(); await pageObject.setEditorTextContent('@'); - expect(pageObject.isMentionListboxOpened()).toBeTrue(); + expect(await pageObject.isMentionListboxOpened()).toBeTrue(); await pageObject.pressEnterKeyInEditor(); expect(pageObject.getMarkdownRenderedTagNames()).toEqual([ @@ -1208,16 +1210,16 @@ describe('RichTextEditorMentionListbox', () => { expect( pageObject.getEditorMentionViewAttributeValues('mention-label') ).toEqual(['username1']); - expect(pageObject.isMentionListboxOpened()).toBeFalse(); + expect(await pageObject.isMentionListboxOpened()).toBeFalse(); }); it('should commit mention into the editor on Tab', async () => { await appendUserMentionConfiguration(element, [ { key: 'user:1', displayName: 'username1' } ]); - expect(pageObject.isMentionListboxOpened()).toBeFalse(); + expect(await pageObject.isMentionListboxOpened()).toBeFalse(); await pageObject.setEditorTextContent('@'); - expect(pageObject.isMentionListboxOpened()).toBeTrue(); + expect(await pageObject.isMentionListboxOpened()).toBeTrue(); await pageObject.pressTabKeyInEditor(); expect(pageObject.getMarkdownRenderedTagNames()).toEqual([ @@ -1227,7 +1229,7 @@ describe('RichTextEditorMentionListbox', () => { expect( pageObject.getEditorMentionViewAttributeValues('mention-label') ).toEqual(['username1']); - expect(pageObject.isMentionListboxOpened()).toBeFalse(); + expect(await pageObject.isMentionListboxOpened()).toBeFalse(); }); it('should close the popup when clicking Escape', async () => { @@ -1235,11 +1237,11 @@ describe('RichTextEditorMentionListbox', () => { { key: 'user:1', displayName: 'username1' }, { key: 'user:2', displayName: 'username2' } ]); - expect(pageObject.isMentionListboxOpened()).toBeFalse(); + expect(await pageObject.isMentionListboxOpened()).toBeFalse(); await pageObject.setEditorTextContent('@'); - expect(pageObject.isMentionListboxOpened()).toBeTrue(); + expect(await pageObject.isMentionListboxOpened()).toBeTrue(); await pageObject.pressEscapeKeyInEditor(); - expect(pageObject.isMentionListboxOpened()).toBeFalse(); + expect(await pageObject.isMentionListboxOpened()).toBeFalse(); }); it('should filter and commit first mention into the editor on Enter', async () => { @@ -1321,7 +1323,7 @@ describe('RichTextEditorMentionListbox', () => { expect( pageObject.getEditorMentionViewAttributeValues('mention-label') ).toEqual(['username2']); - expect(pageObject.isMentionListboxOpened()).toBeFalse(); + expect(await pageObject.isMentionListboxOpened()).toBeFalse(); }); it('should commit second option on arrow key down and tab', async () => { @@ -1340,7 +1342,7 @@ describe('RichTextEditorMentionListbox', () => { expect( pageObject.getEditorMentionViewAttributeValues('mention-label') ).toEqual(['username2']); - expect(pageObject.isMentionListboxOpened()).toBeFalse(); + expect(await pageObject.isMentionListboxOpened()).toBeFalse(); }); it('focus out from the editor should close the mention popup', async () => { @@ -1350,11 +1352,11 @@ describe('RichTextEditorMentionListbox', () => { ]); await pageObject.setEditorTextContent('@'); - expect(pageObject.isMentionListboxOpened()).toBeTrue(); + expect(await pageObject.isMentionListboxOpened()).toBeTrue(); await pageObject.focusOutEditor(); - expect(pageObject.isMentionListboxOpened()).toBeFalse(); + expect(await pageObject.isMentionListboxOpened()).toBeFalse(); }); // WebKit skipped, see https://github.com/ni/nimble/issues/1938 @@ -1365,11 +1367,11 @@ describe('RichTextEditorMentionListbox', () => { ]); await pageObject.setEditorTextContent('@'); - expect(pageObject.isMentionListboxOpened()).toBeTrue(); + expect(await pageObject.isMentionListboxOpened()).toBeTrue(); await pageObject.setDisabled(true); - expect(pageObject.isMentionListboxOpened()).toBeFalse(); + expect(await pageObject.isMentionListboxOpened()).toBeFalse(); }); }); @@ -1383,14 +1385,14 @@ describe('RichTextEditorMentionListbox', () => { ] ); await pageObject.setEditorTextContent('@'); - expect(pageObject.isMentionListboxOpened()).toBeTrue(); + expect(await pageObject.isMentionListboxOpened()).toBeTrue(); expect(pageObject.getMentionListboxItemsName()).toEqual([ 'username1', 'username2' ]); element.removeChild(userMentionElement); await waitForUpdatesAsync(); - expect(pageObject.isMentionListboxOpened()).toBeFalse(); + expect(await pageObject.isMentionListboxOpened()).toBeFalse(); expect(pageObject.getMentionListboxItemsName()).toEqual([]); }); @@ -1409,7 +1411,7 @@ describe('RichTextEditorMentionListbox', () => { expect(pageObject.getMentionListboxItemsName()).toEqual([ 'username2' ]); - expect(pageObject.isMentionListboxOpened()).toBeTrue(); + expect(await pageObject.isMentionListboxOpened()).toBeTrue(); }); it('should update mention popup list when mapping elements get replaced', async () => { @@ -1434,7 +1436,7 @@ describe('RichTextEditorMentionListbox', () => { 'username3', 'username4' ]); - expect(pageObject.isMentionListboxOpened()).toBeTrue(); + expect(await pageObject.isMentionListboxOpened()).toBeTrue(); }); it('should close mention popup when updating to invalid `pattern`', async () => { @@ -1453,7 +1455,7 @@ describe('RichTextEditorMentionListbox', () => { userMentionElement.pattern = 'invalid'; await waitForUpdatesAsync(); expect(pageObject.getMentionListboxItemsName()).toEqual([]); - expect(pageObject.isMentionListboxOpened()).toBeFalse(); + expect(await pageObject.isMentionListboxOpened()).toBeFalse(); }); it('should update mention popup list when updating `display-name`', async () => { @@ -1475,7 +1477,7 @@ describe('RichTextEditorMentionListbox', () => { 'updated-name', 'username2' ]); - expect(pageObject.isMentionListboxOpened()).toBeTrue(); + expect(await pageObject.isMentionListboxOpened()).toBeTrue(); }); it('should update mention popup list when updating valid `key`', async () => { @@ -1484,7 +1486,7 @@ describe('RichTextEditorMentionListbox', () => { [{ key: 'invalid', displayName: 'username1' }] ); await pageObject.setEditorTextContent('@'); - expect(pageObject.isMentionListboxOpened()).toBeFalse(); + expect(await pageObject.isMentionListboxOpened()).toBeFalse(); expect(pageObject.getMentionListboxItemsName()).toEqual([]); mappingElements[0]!.key = 'user:1'; // After the first wait, `activeMappingConfigs` is updated, @@ -1495,7 +1497,7 @@ describe('RichTextEditorMentionListbox', () => { expect(pageObject.getMentionListboxItemsName()).toEqual([ 'username1' ]); - expect(pageObject.isMentionListboxOpened()).toBeTrue(); + expect(await pageObject.isMentionListboxOpened()).toBeTrue(); }); it('should close mention popup when updating to invalid `key`', async () => { @@ -1514,7 +1516,7 @@ describe('RichTextEditorMentionListbox', () => { mappingElements[0]!.key = 'invalid'; await waitForUpdatesAsync(); expect(pageObject.getMentionListboxItemsName()).toEqual([]); - expect(pageObject.isMentionListboxOpened()).toBeFalse(); + expect(await pageObject.isMentionListboxOpened()).toBeFalse(); }); it('should retain mention popup list position in the same cursor position when configuration got updated', async () => { @@ -1539,33 +1541,55 @@ describe('RichTextEditorMentionListbox', () => { 'New user', 'username2' ]); - expect(pageObject.isMentionListboxOpened()).toBeTrue(); + expect(await pageObject.isMentionListboxOpened()).toBeTrue(); }); - // Intermittent, see https://github.com/ni/nimble/issues/2219 - xit('should show mention popup for multiple mention configuration elements', async () => { - await appendUserMentionConfiguration(element, [ - { key: 'user:1', displayName: 'username1' }, - { key: 'user:2', displayName: 'username2' } - ]); - await pageObject.setEditorTextContent('@'); - expect(pageObject.isMentionListboxOpened()).toBeTrue(); - expect(pageObject.getMentionListboxItemsName()).toEqual([ - 'username1', - 'username2' - ]); - await pageObject.clickMentionListboxOption(0); - await appendTestMentionConfiguration(element, [ - { key: 'test:1', displayName: 'testname1' }, - { key: 'test:2', displayName: 'testname2' } - ]); - await pageObject.setEditorTextContent('!'); - expect(pageObject.isMentionListboxOpened()).toBeTrue(); - expect(pageObject.getMentionListboxItemsName()).toEqual([ - 'testname1', - 'testname2' - ]); - }); + for (let i = 0; i < 2000; i++) { + // Intermittent, see https://github.com/ni/nimble/issues/2219 + // eslint-disable-next-line no-restricted-globals + fit( + 'should show mention popup for multiple mention configuration elements' + + ` ${i}`, + async () => { + await appendUserMentionConfiguration(element, [ + { key: 'user:1', displayName: 'username1' }, + { key: 'user:2', displayName: 'username2' } + ]); + + await pageObject.setEditorTextContent('@'); + + expect(pageObject.getMentionListboxItemsName()).toEqual([ + 'username1', + 'username2' + ]); + // eslint-disable-next-line no-console + console.log('1', await pageObject.isMentionListboxOpened()); + expect( + await pageObject.isMentionListboxOpened() + ).toBeTrue(); + + // await pageObject.clickMentionListboxOption(0); + await pageObject.sliceEditorContent(0, 2); + + await appendTestMentionConfiguration(element, [ + { key: 'test:1', displayName: 'testname1' }, + { key: 'test:2', displayName: 'testname2' } + ]); + + await pageObject.setEditorTextContent('!'); + + expect(pageObject.getMentionListboxItemsName()).toEqual([ + 'testname1', + 'testname2' + ]); + // eslint-disable-next-line no-console + console.log('2', await pageObject.isMentionListboxOpened()); + expect( + await pageObject.isMentionListboxOpened() + ).toBeTrue(); + } + ); + } it('mention listbox should be closed when cursor position is moved to start and configuration dynamically changes', async () => { const { mappingElements } = await appendUserMentionConfiguration( @@ -1573,12 +1597,12 @@ describe('RichTextEditorMentionListbox', () => { [{ key: 'user:1', displayName: 'user name1' }] ); await pageObject.setEditorTextContent('@user'); - expect(pageObject.isMentionListboxOpened()).toBeTrue(); + expect(await pageObject.isMentionListboxOpened()).toBeTrue(); await pageObject.setCursorPosition(1); - expect(pageObject.isMentionListboxOpened()).toBeFalse(); + expect(await pageObject.isMentionListboxOpened()).toBeFalse(); mappingElements[0]!.displayName = 'user name2'; await waitForUpdatesAsync(); - expect(pageObject.isMentionListboxOpened()).toBeFalse(); + expect(await pageObject.isMentionListboxOpened()).toBeFalse(); }); }); }); diff --git a/packages/nimble-components/src/rich-text/models/tests/markdown-serializer.spec.ts b/packages/nimble-components/src/rich-text/models/tests/markdown-serializer.spec.ts index 2e06cfb9f1..3dc19dee42 100644 --- a/packages/nimble-components/src/rich-text/models/tests/markdown-serializer.spec.ts +++ b/packages/nimble-components/src/rich-text/models/tests/markdown-serializer.spec.ts @@ -1,3 +1,6 @@ +/* eslint-disable no-useless-concat */ +/* eslint-disable @typescript-eslint/restrict-plus-operands */ +/* eslint-disable @typescript-eslint/no-loop-func */ import { html } from '@microsoft/fast-element'; import type { RichTextEditor } from '../../editor'; import { fixture, type Fixture } from '../../../utilities/tests/fixture'; @@ -337,14 +340,19 @@ Plain text 3`); `); }); - // Intermittent, see https://github.com/ni/nimble/issues/2219 - xit('Mention node', async () => { - await appendUserMentionConfiguration(element, [ - { key: 'user:1', displayName: 'username1' } - ]); - await commitFirstMentionBoxOptionIntoEditor('@'); - expect(element.getMarkdown()).toEqual(' '); - }); + for (let i = 0; i < 2000; i++) { + // Intermittent, see https://github.com/ni/nimble/issues/2219 + // eslint-disable-next-line no-restricted-globals + fit('Mention node' + `${i}`, async () => { + await appendUserMentionConfiguration(element, [ + { key: 'user:1', displayName: 'username1' } + ]); + + await commitFirstMentionBoxOptionIntoEditor('@'); + + expect(element.getMarkdown()).toEqual(' '); + }); + } it('Multiple Mention node of same type', async () => { await appendUserMentionConfiguration(element, [