From bfa3e3683f6e2e8a2ec6807fcdde7f15ae265a3a Mon Sep 17 00:00:00 2001 From: m-akinc <7282195+m-akinc@users.noreply.github.com> Date: Tue, 8 Oct 2024 12:23:46 -0500 Subject: [PATCH 1/6] Disable intermittent test (#2427) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Pull Request ## ๐Ÿคจ Rationale Test failed on unrelated PR build. ## ๐Ÿ‘ฉโ€๐Ÿ’ป Implementation Skipping test on Webkit (can disable fully if it later fails on another browser). Filed #2426 to track it. ## ๐Ÿงช Testing N/A --------- Co-authored-by: Milan Raj --- ...le-components-b21543ab-0fd2-4721-a028-5ba754ec52c2.json | 7 +++++++ .../src/rich-text/models/tests/markdown-serializer.spec.ts | 3 ++- packages/storybook/.storybook/preview.js | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 change/@ni-nimble-components-b21543ab-0fd2-4721-a028-5ba754ec52c2.json diff --git a/change/@ni-nimble-components-b21543ab-0fd2-4721-a028-5ba754ec52c2.json b/change/@ni-nimble-components-b21543ab-0fd2-4721-a028-5ba754ec52c2.json new file mode 100644 index 0000000000..c3697a489c --- /dev/null +++ b/change/@ni-nimble-components-b21543ab-0fd2-4721-a028-5ba754ec52c2.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "Disable intermittent test", + "packageName": "@ni/nimble-components", + "email": "7282195+m-akinc@users.noreply.github.com", + "dependentChangeType": "none" +} 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 e32469d4d8..2e06cfb9f1 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 @@ -358,7 +358,8 @@ Plain text 3`); expect(element.getMarkdown()).toEqual(' '); }); - it('Multiple Mention node of different type', async () => { + // Intermittent on Webkit (at least), see https://github.com/ni/nimble/issues/2426 + it('Multiple Mention node of different type #SkipWebkit', async () => { await appendUserMentionConfiguration(element, [ { key: 'user:1', displayName: 'username1' } ]); diff --git a/packages/storybook/.storybook/preview.js b/packages/storybook/.storybook/preview.js index e5fd5a9ee7..9572216033 100644 --- a/packages/storybook/.storybook/preview.js +++ b/packages/storybook/.storybook/preview.js @@ -105,5 +105,5 @@ configureActions({ depth: 1 }); -// Update the GUID on this line to trigger a turbosnap full rebuild: 9236157e-5f33-411d-aede-26045b7d9d58 +// Update the GUID on this line to trigger a turbosnap full rebuild: acea85eb-3c9d-49c3-ae4f-7c12f82bc5a0 // See https://www.chromatic.com/docs/turbosnap/#full-rebuilds From 52a54083a9350199fa16a7344e83efd81d0549ce Mon Sep 17 00:00:00 2001 From: rajsite Date: Tue, 8 Oct 2024 12:39:50 -0500 Subject: [PATCH 2/6] applying package updates [skip ci] --- ...ents-b21543ab-0fd2-4721-a028-5ba754ec52c2.json | 7 ------- packages/nimble-components/CHANGELOG.json | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 7 deletions(-) delete mode 100644 change/@ni-nimble-components-b21543ab-0fd2-4721-a028-5ba754ec52c2.json diff --git a/change/@ni-nimble-components-b21543ab-0fd2-4721-a028-5ba754ec52c2.json b/change/@ni-nimble-components-b21543ab-0fd2-4721-a028-5ba754ec52c2.json deleted file mode 100644 index c3697a489c..0000000000 --- a/change/@ni-nimble-components-b21543ab-0fd2-4721-a028-5ba754ec52c2.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "none", - "comment": "Disable intermittent test", - "packageName": "@ni/nimble-components", - "email": "7282195+m-akinc@users.noreply.github.com", - "dependentChangeType": "none" -} diff --git a/packages/nimble-components/CHANGELOG.json b/packages/nimble-components/CHANGELOG.json index 50b547639b..25929bc285 100644 --- a/packages/nimble-components/CHANGELOG.json +++ b/packages/nimble-components/CHANGELOG.json @@ -1,6 +1,21 @@ { "name": "@ni/nimble-components", "entries": [ + { + "date": "Tue, 08 Oct 2024 17:39:50 GMT", + "version": "32.2.10", + "tag": "@ni/nimble-components_v32.2.10", + "comments": { + "none": [ + { + "author": "7282195+m-akinc@users.noreply.github.com", + "package": "@ni/nimble-components", + "commit": "bfa3e3683f6e2e8a2ec6807fcdde7f15ae265a3a", + "comment": "Disable intermittent test" + } + ] + } + }, { "date": "Tue, 01 Oct 2024 00:35:26 GMT", "version": "32.2.10", From 8aa7c1ee2a6fae665419b8a8d9c8f8e7e977c424 Mon Sep 17 00:00:00 2001 From: mollykreis <20542556+mollykreis@users.noreply.github.com> Date: Wed, 9 Oct 2024 14:08:16 -0500 Subject: [PATCH 3/6] Radio group error (#2423) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Pull Request ## ๐Ÿคจ Rationale Includes `nimble-component` implementation for #2019 ## ๐Ÿ‘ฉโ€๐Ÿ’ป Implementation - Adds `error-visible` and `error-text` attributes to the `nimble-radio-group` - Updates the radio group template to include an error icon and error text - Note: This required forking FAST's template - Updated radio group styling to match visual design ## ๐Ÿงช Testing - Manually tested in storybook - Forked FAST's radio group tests since the template was forked - Updated matrix tests ## โœ… Checklist - [ ] I have updated the project documentation to reflect my changes or determined no changes are needed. --- ...-e5bfd621-8ece-4d9c-a6ea-eb128a70e8ac.json | 7 + .../src/radio-group/index.ts | 12 +- .../src/radio-group/styles.ts | 17 + .../src/radio-group/template.ts | 37 ++ .../tests/radio-group.foundation.spec.ts | 505 ++++++++++++++++++ .../radio-group/radio-group-matrix.stories.ts | 18 +- .../nimble/radio-group/radio-group.stories.ts | 23 +- packages/storybook/src/utilities/storybook.ts | 2 +- 8 files changed, 612 insertions(+), 9 deletions(-) create mode 100644 change/@ni-nimble-components-e5bfd621-8ece-4d9c-a6ea-eb128a70e8ac.json create mode 100644 packages/nimble-components/src/radio-group/template.ts create mode 100644 packages/nimble-components/src/radio-group/tests/radio-group.foundation.spec.ts diff --git a/change/@ni-nimble-components-e5bfd621-8ece-4d9c-a6ea-eb128a70e8ac.json b/change/@ni-nimble-components-e5bfd621-8ece-4d9c-a6ea-eb128a70e8ac.json new file mode 100644 index 0000000000..5d44c0b333 --- /dev/null +++ b/change/@ni-nimble-components-e5bfd621-8ece-4d9c-a6ea-eb128a70e8ac.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "Implement error states on nimble-radio-group", + "packageName": "@ni/nimble-components", + "email": "20542556+mollykreis@users.noreply.github.com", + "dependentChangeType": "patch" +} diff --git a/packages/nimble-components/src/radio-group/index.ts b/packages/nimble-components/src/radio-group/index.ts index deeb9f87f0..fee994518a 100644 --- a/packages/nimble-components/src/radio-group/index.ts +++ b/packages/nimble-components/src/radio-group/index.ts @@ -1,10 +1,12 @@ import { RadioGroup as FoundationRadioGroup, - radioGroupTemplate as template, DesignSystem } from '@microsoft/fast-foundation'; import { Orientation } from '@microsoft/fast-web-utilities'; +import { attr } from '@microsoft/fast-element'; import { styles } from './styles'; +import { template } from './template'; +import type { ErrorPattern } from '../patterns/error/types'; declare global { interface HTMLElementTagNameMap { @@ -17,7 +19,13 @@ export { Orientation }; /** * A nimble-styled grouping element for radio buttons */ -export class RadioGroup extends FoundationRadioGroup {} +export class RadioGroup extends FoundationRadioGroup implements ErrorPattern { + @attr({ attribute: 'error-text' }) + public errorText?: string; + + @attr({ attribute: 'error-visible', mode: 'boolean' }) + public errorVisible = false; +} const nimbleRadioGroup = RadioGroup.compose({ baseName: 'radio-group', diff --git a/packages/nimble-components/src/radio-group/styles.ts b/packages/nimble-components/src/radio-group/styles.ts index 37d2ac8d93..ca33db0192 100644 --- a/packages/nimble-components/src/radio-group/styles.ts +++ b/packages/nimble-components/src/radio-group/styles.ts @@ -4,15 +4,20 @@ import { controlLabelDisabledFontColor, controlLabelFont, controlLabelFontColor, + controlLabelFontLineHeight, + smallPadding, standardPadding } from '../theme-provider/design-tokens'; +import { styles as errorStyles } from '../patterns/error/styles'; export const styles = css` ${display('inline-block')} + ${errorStyles} .positioning-region { display: flex; gap: ${standardPadding}; + position: relative; } :host([orientation='vertical']) .positioning-region { @@ -23,6 +28,13 @@ export const styles = css` flex-direction: row; } + .label-container { + display: flex; + height: ${controlLabelFontLineHeight}; + gap: ${smallPadding}; + margin-bottom: ${smallPadding}; + } + slot[name='label'] { font: ${controlLabelFont}; color: ${controlLabelFontColor}; @@ -31,4 +43,9 @@ export const styles = css` :host([disabled]) slot[name='label'] { color: ${controlLabelDisabledFontColor}; } + + .error-icon { + margin-left: auto; + margin-right: ${smallPadding}; + } `; diff --git a/packages/nimble-components/src/radio-group/template.ts b/packages/nimble-components/src/radio-group/template.ts new file mode 100644 index 0000000000..4d538f2490 --- /dev/null +++ b/packages/nimble-components/src/radio-group/template.ts @@ -0,0 +1,37 @@ +import { elements, html, slotted } from '@microsoft/fast-element'; +import { Orientation } from '@microsoft/fast-web-utilities'; +import type { RadioGroup } from '.'; +import { errorTextTemplate } from '../patterns/error/template'; +import { iconExclamationMarkTag } from '../icons/exclamation-mark'; + +/* eslint-disable @typescript-eslint/indent */ +export const template = html` + +`; diff --git a/packages/nimble-components/src/radio-group/tests/radio-group.foundation.spec.ts b/packages/nimble-components/src/radio-group/tests/radio-group.foundation.spec.ts new file mode 100644 index 0000000000..cdf0d1b8eb --- /dev/null +++ b/packages/nimble-components/src/radio-group/tests/radio-group.foundation.spec.ts @@ -0,0 +1,505 @@ +// Based on tests in FAST repo: https://github.com/microsoft/fast/blob/913c27e7e8503de1f7cd50bdbc9388134f52ef5d/packages/web-components/fast-foundation/src/radio-group/radio-group.spec.ts + +import { DOM } from '@microsoft/fast-element'; +import { + Radio, + radioTemplate as itemTemplate +} from '@microsoft/fast-foundation'; +import { Orientation } from '@microsoft/fast-web-utilities'; +import { RadioGroup } from '..'; +import { fixture } from '../../utilities/tests/fixture'; +import { template } from '../template'; +import { radioTag } from '../../radio'; + +// eslint-disable-next-line @typescript-eslint/naming-convention +const FASTRadioGroup = RadioGroup.compose({ + baseName: 'radio-group', + template +}); + +// TODO: Need to add tests for keyboard handling & focus management +describe('Radio Group', () => { + // eslint-disable-next-line @typescript-eslint/naming-convention + const FASTRadio = Radio.compose({ + baseName: 'radio', + template: itemTemplate + }); + + async function setup(): Promise<{ + element: RadioGroup, + connect: () => Promise, + disconnect: () => Promise, + parent: HTMLElement, + radio1: Radio, + radio2: Radio, + radio3: Radio + }> { + const { element, connect, disconnect, parent } = await fixture([ + FASTRadioGroup(), + FASTRadio() + ]); + + const radio1 = document.createElement(radioTag); + const radio2 = document.createElement(radioTag); + const radio3 = document.createElement(radioTag); + + radio1.className = 'one'; + radio2.className = 'two'; + radio3.className = 'three'; + + element.appendChild(radio1); + element.appendChild(radio2); + element.appendChild(radio3); + + return { element, connect, disconnect, parent, radio1, radio2, radio3 }; + } + + it('should have a role of `radiogroup`', async () => { + const { element, connect, disconnect } = await setup(); + + await connect(); + + expect(element.getAttribute('role')).toEqual('radiogroup'); + + await disconnect(); + }); + + it("should set a `horizontal` class on the 'positioning-region' when an orientation of `horizontal` is provided", async () => { + const { element, connect, disconnect } = await setup(); + + element.orientation = Orientation.horizontal; + + await connect(); + + expect( + element.shadowRoot + ?.querySelector('.positioning-region') + ?.classList.contains('horizontal') + ).toBeTrue(); + + await disconnect(); + }); + + it("should set a `vertical` class on the 'positioning-region' when an orientation of `vertical` is provided", async () => { + const { element, connect, disconnect } = await setup(); + + element.orientation = Orientation.vertical; + + await connect(); + + expect( + element.shadowRoot + ?.querySelector('.positioning-region') + ?.classList.contains('vertical') + ).toBeTrue(); + + await disconnect(); + }); + + it("should set a default class on the 'positioning-region' of `horizontal` when no orientation is provided", async () => { + const { element, connect, disconnect } = await setup(); + + await connect(); + + expect( + element.shadowRoot + ?.querySelector('.positioning-region') + ?.classList.contains('horizontal') + ).toBeTrue(); + + await disconnect(); + }); + + it('should set the `aria-disabled` attribute equal to the `disabled` value', async () => { + const { element, connect, disconnect } = await setup(); + + element.disabled = true; + + await connect(); + + expect(element.getAttribute('aria-disabled')).toBe('true'); + + element.disabled = false; + + await DOM.nextUpdate(); + + expect(element.getAttribute('aria-disabled')).toBe('false'); + + await disconnect(); + }); + + it('should NOT set a default `aria-disabled` value when `disabled` is not defined', async () => { + const { element, connect, disconnect } = await setup(); + + await connect(); + + expect(element.getAttribute('aria-disabled')).toBe(null); + + await disconnect(); + }); + + it('should set all child radio elements to disabled when the`disabled` is passed', async () => { + const { element, connect, disconnect } = await setup(); + element.disabled = true; + + await connect(); + await DOM.nextUpdate(); + + expect(element.querySelector('.one')!.disabled).toBeTrue(); + expect(element.querySelector('.two')!.disabled).toBeTrue(); + expect(element.querySelector('.three')!.disabled).toBeTrue(); + + expect( + element.querySelector('.one')?.getAttribute('aria-disabled') + ).toBe('true'); + expect( + element.querySelector('.two')?.getAttribute('aria-disabled') + ).toBe('true'); + expect( + element.querySelector('.three')?.getAttribute('aria-disabled') + ).toBe('true'); + + await disconnect(); + }); + + it('should set the `aria-readonly` attribute equal to the `readonly` value', async () => { + const { element, connect, disconnect } = await fixture(FASTRadioGroup()); + + element.readOnly = true; + + await connect(); + + expect(element.getAttribute('aria-readonly')).toBe('true'); + + element.readOnly = false; + + await DOM.nextUpdate(); + + expect(element.getAttribute('aria-readonly')).toBe('false'); + + await disconnect(); + }); + + it('should NOT set a default `aria-readonly` value when `readonly` is not defined', async () => { + const { element, connect, disconnect } = await fixture(FASTRadioGroup()); + + await connect(); + + expect(element.getAttribute('aria-readonly')).toBe(null); + + await disconnect(); + }); + + it('should set all child radio elements to readonly when the`readonly` is passed', async () => { + const { element, connect, disconnect } = await setup(); + element.readOnly = true; + + await connect(); + await DOM.nextUpdate(); + + expect(element.querySelector('.one')!.readOnly).toBeTrue(); + expect(element.querySelector('.two')!.readOnly).toBeTrue(); + expect(element.querySelector('.three')!.readOnly).toBeTrue(); + + expect( + element.querySelector('.one')?.getAttribute('aria-readonly') + ).toBe('true'); + expect( + element.querySelector('.two')?.getAttribute('aria-readonly') + ).toBe('true'); + expect( + element.querySelector('.three')?.getAttribute('aria-readonly') + ).toBe('true'); + + await disconnect(); + }); + + it('should set tabindex of 0 to a child radio with a matching `value`', async () => { + const { element, connect, disconnect } = await fixture([ + FASTRadioGroup(), + FASTRadio() + ]); + + element.value = 'baz'; + + const radio1 = document.createElement(radioTag); + const radio2 = document.createElement(radioTag); + const radio3 = document.createElement(radioTag); + + radio1.className = 'one'; + radio2.className = 'two'; + radio3.className = 'three'; + + radio1.value = 'foo'; + radio2.value = 'bar'; + radio3.value = 'baz'; + + element.appendChild(radio1); + element.appendChild(radio2); + element.appendChild(radio3); + + await connect(); + await DOM.nextUpdate(); + + expect( + element.querySelectorAll(radioTag)[2]!.getAttribute('tabindex') + ).toBe('0'); + + await disconnect(); + }); + + it('should NOT set tabindex of 0 to a child radio if its value does not match the radiogroup `value`', async () => { + const { element, connect, disconnect } = await fixture([ + FASTRadioGroup(), + FASTRadio() + ]); + + element.value = 'baz'; + + const radio1 = document.createElement(radioTag); + const radio2 = document.createElement(radioTag); + const radio3 = document.createElement(radioTag); + + radio1.className = 'one'; + radio2.className = 'two'; + radio3.className = 'three'; + + radio1.value = 'foo'; + radio2.value = 'bar'; + radio3.value = 'baz'; + + element.appendChild(radio1); + element.appendChild(radio2); + element.appendChild(radio3); + + await connect(); + await DOM.nextUpdate(); + + expect( + element.querySelectorAll(radioTag)[0]!.getAttribute('tabindex') + ).toBe('-1'); + expect( + element.querySelectorAll(radioTag)[1]!.getAttribute('tabindex') + ).toBe('-1'); + + await disconnect(); + }); + + it('should set a child radio with a matching `value` to `checked`', async () => { + const { element, connect, disconnect } = await fixture([ + FASTRadioGroup(), + FASTRadio() + ]); + + element.value = 'baz'; + + const radio1 = document.createElement(radioTag); + const radio2 = document.createElement(radioTag); + const radio3 = document.createElement(radioTag); + + radio1.className = 'one'; + radio2.className = 'two'; + radio3.className = 'three'; + + radio1.value = 'foo'; + radio2.value = 'bar'; + radio3.value = 'baz'; + + element.appendChild(radio1); + element.appendChild(radio2); + element.appendChild(radio3); + + await connect(); + await DOM.nextUpdate(); + + expect(element.querySelectorAll(radioTag)[2]!.checked).toBeTrue(); + expect( + element.querySelectorAll(radioTag)[2]!.getAttribute('aria-checked') + ).toBe('true'); + + await disconnect(); + }); + + it('should set a child radio with a matching `value` to `checked` when value changes', async () => { + const { element, connect, disconnect } = await fixture([ + FASTRadioGroup(), + FASTRadio() + ]); + + element.value = 'baz'; + + const radio1 = document.createElement(radioTag); + const radio2 = document.createElement(radioTag); + const radio3 = document.createElement(radioTag); + + radio1.className = 'one'; + radio2.className = 'two'; + radio3.className = 'three'; + + radio1.value = 'foo'; + radio2.value = 'bar'; + radio3.value = 'baz'; + + element.appendChild(radio1); + element.appendChild(radio2); + element.appendChild(radio3); + + await connect(); + await DOM.nextUpdate(); + + element.value = 'foo'; + + await DOM.nextUpdate(); + + expect(element.querySelectorAll(radioTag)[0]!.checked).toBeTrue(); + expect( + element.querySelectorAll(radioTag)[0]!.getAttribute('aria-checked') + ).toBe('true'); + + await disconnect(); + }); + + it('should mark the last radio defaulted to checked as checked, the rest should not be checked', async () => { + const { element, connect, disconnect } = await fixture([ + FASTRadioGroup(), + FASTRadio() + ]); + + const radio1 = document.createElement(radioTag); + const radio2 = document.createElement(radioTag); + const radio3 = document.createElement(radioTag); + + radio1.className = 'one'; + radio2.className = 'two'; + radio3.className = 'three'; + + radio1.value = 'foo'; + radio2.value = 'bar'; + radio3.value = 'baz'; + + radio2.setAttribute('checked', ''); + radio3.setAttribute('checked', ''); + + element.appendChild(radio1); + element.appendChild(radio2); + element.appendChild(radio3); + + await connect(); + await DOM.nextUpdate(); + + const radios: NodeList = element.querySelectorAll(radioTag); + expect((radios[2] as HTMLInputElement).checked).toBeTrue(); + expect((radios[1] as HTMLInputElement).checked).toBeFalse(); + + await disconnect(); + }); + + it('should mark radio matching value on radio-group over any checked attributes', async () => { + const { element, connect, disconnect } = await fixture([ + FASTRadioGroup(), + FASTRadio() + ]); + + element.value = 'bar'; + + const radio1 = document.createElement(radioTag); + const radio2 = document.createElement(radioTag); + const radio3 = document.createElement(radioTag); + + radio1.className = 'one'; + radio2.className = 'two'; + radio3.className = 'three'; + + radio1.value = 'foo'; + radio2.value = 'bar'; + radio3.value = 'baz'; + + radio2.setAttribute('checked', ''); + radio3.setAttribute('checked', ''); + + element.appendChild(radio1); + element.appendChild(radio2); + element.appendChild(radio3); + + await connect(); + await DOM.nextUpdate(); + + const radios: NodeList = element.querySelectorAll(radioTag); + expect((radios[1] as HTMLInputElement).checked).toBeTrue(); + + // radio-group explicitly sets non-matching radio's checked to false if a value match was found, + // but the attribute should still persist. + expect( + (radios[2] as HTMLInputElement).hasAttribute('checked') + ).toBeTrue(); + expect((radios[2] as HTMLInputElement).checked).toBeFalse(); + + await disconnect(); + }); + + it('should NOT set a child radio to `checked` if its value does not match the radiogroup `value`', async () => { + const { element, connect, disconnect } = await fixture([ + FASTRadioGroup(), + FASTRadio() + ]); + + element.value = 'baz'; + + const radio1 = document.createElement(radioTag); + const radio2 = document.createElement(radioTag); + const radio3 = document.createElement(radioTag); + + radio1.className = 'one'; + radio2.className = 'two'; + radio3.className = 'three'; + + radio1.value = 'foo'; + radio2.value = 'bar'; + radio3.value = 'baz'; + + element.appendChild(radio1); + element.appendChild(radio2); + element.appendChild(radio3); + + await connect(); + await DOM.nextUpdate(); + + expect(element.querySelectorAll(radioTag)[0]!.checked).toBeFalse(); + expect( + element.querySelectorAll(radioTag)[0]!.getAttribute('aria-checked') + ).toBe('false'); + + expect(element.querySelectorAll(radioTag)[1]!.checked).toBeFalse(); + expect( + element.querySelectorAll(radioTag)[1]!.getAttribute('aria-checked') + ).toBe('false'); + + await disconnect(); + }); + + it('should allow resetting of elements by the parent form', async () => { + const { element, connect, disconnect, parent, radio1, radio2, radio3 } = await setup(); + + radio2.setAttribute('checked', ''); + + const form = document.createElement('form'); + form.appendChild(element); + parent.appendChild(form); + + await connect(); + + radio1.checked = true; + + expect(!!radio1.checked).toBeTrue(); + expect(!!radio2.checked).toBeFalse(); + expect(!!radio3.checked).toBeFalse(); + + form.reset(); + + expect(!!radio1.checked).toBeFalse(); + expect(!!radio2.checked).toBeTrue(); + expect(!!radio3.checked).toBeFalse(); + + await disconnect(); + }); +}); diff --git a/packages/storybook/src/nimble/radio-group/radio-group-matrix.stories.ts b/packages/storybook/src/nimble/radio-group/radio-group-matrix.stories.ts index 3f43b7c573..ce58d37df9 100644 --- a/packages/storybook/src/nimble/radio-group/radio-group-matrix.stories.ts +++ b/packages/storybook/src/nimble/radio-group/radio-group-matrix.stories.ts @@ -1,6 +1,7 @@ import type { StoryFn, Meta } from '@storybook/html'; import { html, ViewTemplate } from '@microsoft/fast-element'; import { Orientation } from '@microsoft/fast-web-utilities'; +import { standardPadding } from '../../../../nimble-components/src/theme-provider/design-tokens'; import { radioTag } from '../../../../nimble-components/src/radio'; import { radioGroupTag } from '../../../../nimble-components/src/radio-group'; import { @@ -8,7 +9,12 @@ import { sharedMatrixParameters, createMatrixThemeStory } from '../../utilities/matrix'; -import { disabledStates, DisabledState } from '../../utilities/states'; +import { + disabledStates, + DisabledState, + errorStates, + ErrorState +} from '../../utilities/states'; import { createStory } from '../../utilities/storybook'; import { hiddenWrapper } from '../../utilities/hidden'; import { textCustomizationWrapper } from '../../utilities/text-customization'; @@ -30,19 +36,23 @@ export default metadata; const component = ( [disabledName, disabled]: DisabledState, - [orientationName, orientation]: OrientationState + [orientationName, orientation]: OrientationState, + [errorName, errorVisible, errorText]: ErrorState ): ViewTemplate => html`<${radioGroupTag} orientation="${() => orientation}" ?disabled="${() => disabled}" + ?error-visible="${() => errorVisible}" + error-text="${() => errorText}" value="1" + style="width: 250px; margin: var(${standardPadding.cssCustomProperty});" > - + <${radioTag} value="1">Option 1 <${radioTag} value="2">Option 2 `; export const radioGroupThemeMatrix: StoryFn = createMatrixThemeStory( - createMatrix(component, [disabledStates, orientationStates]) + createMatrix(component, [disabledStates, orientationStates, errorStates]) ); export const hiddenRadioGroup: StoryFn = createStory( diff --git a/packages/storybook/src/nimble/radio-group/radio-group.stories.ts b/packages/storybook/src/nimble/radio-group/radio-group.stories.ts index 98825d3fb5..48a72f018b 100644 --- a/packages/storybook/src/nimble/radio-group/radio-group.stories.ts +++ b/packages/storybook/src/nimble/radio-group/radio-group.stories.ts @@ -8,6 +8,8 @@ import { apiCategory, createUserSelectedThemeStory, disabledDescription, + errorTextDescription, + errorVisibleDescription, slottedLabelDescription } from '../../utilities/storybook'; @@ -19,6 +21,8 @@ interface RadioGroupArgs { value: string; buttons: undefined; change: undefined; + errorVisible: boolean; + errorText: string; } interface RadioArgs { @@ -48,7 +52,10 @@ export const radioGroup: StoryObj = { orientation="${x => x.orientation}" ?disabled="${x => x.disabled}" name="${x => x.name}" - value="${x => x.value}" + value="${x => (x.value === 'none' ? undefined : x.value)}" + ?error-visible="${x => x.errorVisible}" + error-text="${x => x.errorText}" + style="min-width: 200px;" > <${radioTag} value="apple">Apple @@ -61,7 +68,9 @@ export const radioGroup: StoryObj = { orientation: Orientation.horizontal, disabled: false, name: 'fruit', - value: 'none' + value: 'none', + errorVisible: false, + errorText: 'Value is invalid' }, argTypes: { value: { @@ -105,6 +114,16 @@ export const radioGroup: StoryObj = { 'Event emitted when the user selects a new value in the radio group.', table: { category: apiCategory.events }, control: false + }, + errorText: { + name: 'error-text', + description: errorTextDescription, + table: { category: apiCategory.attributes } + }, + errorVisible: { + name: 'error-visible', + description: errorVisibleDescription, + table: { category: apiCategory.attributes } } } }; diff --git a/packages/storybook/src/utilities/storybook.ts b/packages/storybook/src/utilities/storybook.ts index a54e48010e..9a79b83c0c 100644 --- a/packages/storybook/src/utilities/storybook.ts +++ b/packages/storybook/src/utilities/storybook.ts @@ -195,7 +195,7 @@ export const placeholderDescription = (options: { }): string => `Placeholder text to display when no value has been entered in the ${options.componentName}.`; export const errorTextDescription = 'A message to be displayed explaining why the value is invalid. Only visible when `error-visible` is set.'; -export const errorVisibleDescription = 'When set to `true`, the `error-text` message will be displayed.'; +export const errorVisibleDescription = 'When set to `true`, an error indicator will be displayed within the control and the `error-text` message will be displayed.'; export const dropdownPositionDescription = (options: { componentName: string From f293446276c82608fd79bf1acb31c78ef1503fac Mon Sep 17 00:00:00 2001 From: rajsite Date: Wed, 9 Oct 2024 14:24:18 -0500 Subject: [PATCH 4/6] applying package updates [skip ci] --- ...-e5bfd621-8ece-4d9c-a6ea-eb128a70e8ac.json | 7 ------ package-lock.json | 22 +++++++++---------- .../nimble-angular/CHANGELOG.json | 15 +++++++++++++ .../nimble-angular/CHANGELOG.md | 10 ++++++++- .../nimble-angular/package.json | 4 ++-- .../spright-angular/CHANGELOG.json | 15 +++++++++++++ .../spright-angular/CHANGELOG.md | 10 ++++++++- .../spright-angular/package.json | 4 ++-- .../NimbleBlazor/CHANGELOG.json | 15 +++++++++++++ .../NimbleBlazor/CHANGELOG.md | 10 ++++++++- .../NimbleBlazor/package.json | 4 ++-- .../SprightBlazor/CHANGELOG.json | 15 +++++++++++++ .../SprightBlazor/CHANGELOG.md | 10 ++++++++- .../SprightBlazor/package.json | 4 ++-- packages/nimble-components/CHANGELOG.json | 15 +++++++++++++ packages/nimble-components/CHANGELOG.md | 10 ++++++++- packages/nimble-components/package.json | 2 +- packages/spright-components/CHANGELOG.json | 15 +++++++++++++ packages/spright-components/CHANGELOG.md | 10 ++++++++- packages/spright-components/package.json | 4 ++-- 20 files changed, 166 insertions(+), 35 deletions(-) delete mode 100644 change/@ni-nimble-components-e5bfd621-8ece-4d9c-a6ea-eb128a70e8ac.json diff --git a/change/@ni-nimble-components-e5bfd621-8ece-4d9c-a6ea-eb128a70e8ac.json b/change/@ni-nimble-components-e5bfd621-8ece-4d9c-a6ea-eb128a70e8ac.json deleted file mode 100644 index 5d44c0b333..0000000000 --- a/change/@ni-nimble-components-e5bfd621-8ece-4d9c-a6ea-eb128a70e8ac.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "minor", - "comment": "Implement error states on nimble-radio-group", - "packageName": "@ni/nimble-components", - "email": "20542556+mollykreis@users.noreply.github.com", - "dependentChangeType": "patch" -} diff --git a/package-lock.json b/package-lock.json index 81d7101357..498bf304c4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27974,7 +27974,7 @@ }, "packages/angular-workspace/nimble-angular": { "name": "@ni/nimble-angular", - "version": "28.2.9", + "version": "28.2.10", "license": "MIT", "dependencies": { "tslib": "^2.2.0" @@ -27985,12 +27985,12 @@ "@angular/forms": "^17.3.12", "@angular/localize": "^17.3.12", "@angular/router": "^17.3.12", - "@ni/nimble-components": "^32.2.10" + "@ni/nimble-components": "^32.3.0" } }, "packages/angular-workspace/spright-angular": { "name": "@ni/spright-angular", - "version": "5.1.10", + "version": "5.1.11", "license": "MIT", "dependencies": { "tslib": "^2.2.0" @@ -27998,7 +27998,7 @@ "peerDependencies": { "@angular/common": "^17.3.12", "@angular/core": "^17.3.12", - "@ni/spright-components": "^4.1.10" + "@ni/spright-components": "^4.1.11" } }, "packages/blazor-workspace": { @@ -28019,10 +28019,10 @@ }, "packages/blazor-workspace/NimbleBlazor": { "name": "@ni/nimble-blazor", - "version": "18.2.9", + "version": "18.2.10", "license": "MIT", "peerDependencies": { - "@ni/nimble-components": "^32.2.10", + "@ni/nimble-components": "^32.3.0", "@ni/nimble-tokens": "^8.3.0", "cross-env": "^7.0.3", "rimraf": "^6.0.0" @@ -28093,10 +28093,10 @@ }, "packages/blazor-workspace/SprightBlazor": { "name": "@ni/spright-blazor", - "version": "2.1.10", + "version": "2.1.11", "license": "MIT", "peerDependencies": { - "@ni/spright-components": "^4.1.10", + "@ni/spright-components": "^4.1.11", "cross-env": "^7.0.3", "rimraf": "^6.0.0" } @@ -28130,7 +28130,7 @@ }, "packages/nimble-components": { "name": "@ni/nimble-components", - "version": "32.2.10", + "version": "32.3.0", "license": "MIT", "dependencies": { "@microsoft/fast-colors": "^5.3.1", @@ -28249,12 +28249,12 @@ }, "packages/spright-components": { "name": "@ni/spright-components", - "version": "4.1.10", + "version": "4.1.11", "license": "MIT", "dependencies": { "@microsoft/fast-element": "^1.12.0", "@microsoft/fast-foundation": "^2.49.6", - "@ni/nimble-components": "^32.2.10", + "@ni/nimble-components": "^32.3.0", "tslib": "^2.2.0" }, "devDependencies": { diff --git a/packages/angular-workspace/nimble-angular/CHANGELOG.json b/packages/angular-workspace/nimble-angular/CHANGELOG.json index 6d2b86556e..787863f1ba 100644 --- a/packages/angular-workspace/nimble-angular/CHANGELOG.json +++ b/packages/angular-workspace/nimble-angular/CHANGELOG.json @@ -1,6 +1,21 @@ { "name": "@ni/nimble-angular", "entries": [ + { + "date": "Wed, 09 Oct 2024 19:24:18 GMT", + "version": "28.2.10", + "tag": "@ni/nimble-angular_v28.2.10", + "comments": { + "patch": [ + { + "author": "beachball", + "package": "@ni/nimble-angular", + "comment": "Bump @ni/nimble-components to v32.3.0", + "commit": "not available" + } + ] + } + }, { "date": "Thu, 26 Sep 2024 18:24:50 GMT", "version": "28.2.9", diff --git a/packages/angular-workspace/nimble-angular/CHANGELOG.md b/packages/angular-workspace/nimble-angular/CHANGELOG.md index 069e108640..5fe2b444f7 100644 --- a/packages/angular-workspace/nimble-angular/CHANGELOG.md +++ b/packages/angular-workspace/nimble-angular/CHANGELOG.md @@ -1,9 +1,17 @@ # Change Log - @ni/nimble-angular - + +## 28.2.10 + +Wed, 09 Oct 2024 19:24:18 GMT + +### Patches + +- Bump @ni/nimble-components to v32.3.0 + ## 28.2.9 Thu, 26 Sep 2024 18:24:50 GMT diff --git a/packages/angular-workspace/nimble-angular/package.json b/packages/angular-workspace/nimble-angular/package.json index 56ca51b2bf..c49fd14dee 100644 --- a/packages/angular-workspace/nimble-angular/package.json +++ b/packages/angular-workspace/nimble-angular/package.json @@ -1,6 +1,6 @@ { "name": "@ni/nimble-angular", - "version": "28.2.9", + "version": "28.2.10", "description": "Angular components for the NI Nimble Design System", "scripts": { "invoke-publish": "npm run invoke-publish:setup && cd ../dist/nimble-angular && npm publish", @@ -32,7 +32,7 @@ "@angular/forms": "^17.3.12", "@angular/localize": "^17.3.12", "@angular/router": "^17.3.12", - "@ni/nimble-components": "^32.2.10" + "@ni/nimble-components": "^32.3.0" }, "dependencies": { "tslib": "^2.2.0" diff --git a/packages/angular-workspace/spright-angular/CHANGELOG.json b/packages/angular-workspace/spright-angular/CHANGELOG.json index 6c9818b4ca..f16cebaf2e 100644 --- a/packages/angular-workspace/spright-angular/CHANGELOG.json +++ b/packages/angular-workspace/spright-angular/CHANGELOG.json @@ -1,6 +1,21 @@ { "name": "@ni/spright-angular", "entries": [ + { + "date": "Wed, 09 Oct 2024 19:24:18 GMT", + "version": "5.1.11", + "tag": "@ni/spright-angular_v5.1.11", + "comments": { + "patch": [ + { + "author": "beachball", + "package": "@ni/spright-angular", + "comment": "Bump @ni/spright-components to v4.1.11", + "commit": "not available" + } + ] + } + }, { "date": "Thu, 26 Sep 2024 18:24:50 GMT", "version": "5.1.10", diff --git a/packages/angular-workspace/spright-angular/CHANGELOG.md b/packages/angular-workspace/spright-angular/CHANGELOG.md index 6c6932f027..b63acbdf8b 100644 --- a/packages/angular-workspace/spright-angular/CHANGELOG.md +++ b/packages/angular-workspace/spright-angular/CHANGELOG.md @@ -1,9 +1,17 @@ # Change Log - @ni/spright-angular - + +## 5.1.11 + +Wed, 09 Oct 2024 19:24:18 GMT + +### Patches + +- Bump @ni/spright-components to v4.1.11 + ## 5.1.10 Thu, 26 Sep 2024 18:24:50 GMT diff --git a/packages/angular-workspace/spright-angular/package.json b/packages/angular-workspace/spright-angular/package.json index 4273d02b94..4526265004 100644 --- a/packages/angular-workspace/spright-angular/package.json +++ b/packages/angular-workspace/spright-angular/package.json @@ -1,6 +1,6 @@ { "name": "@ni/spright-angular", - "version": "5.1.10", + "version": "5.1.11", "description": "Angular components for NI Spright", "scripts": { "invoke-publish": "npm run invoke-publish:setup && cd ../dist/spright-angular && npm publish", @@ -24,7 +24,7 @@ "peerDependencies": { "@angular/common": "^17.3.12", "@angular/core": "^17.3.12", - "@ni/spright-components": "^4.1.10" + "@ni/spright-components": "^4.1.11" }, "dependencies": { "tslib": "^2.2.0" diff --git a/packages/blazor-workspace/NimbleBlazor/CHANGELOG.json b/packages/blazor-workspace/NimbleBlazor/CHANGELOG.json index 8826b8d792..80bc5f6bb5 100644 --- a/packages/blazor-workspace/NimbleBlazor/CHANGELOG.json +++ b/packages/blazor-workspace/NimbleBlazor/CHANGELOG.json @@ -1,6 +1,21 @@ { "name": "@ni/nimble-blazor", "entries": [ + { + "date": "Wed, 09 Oct 2024 19:24:18 GMT", + "version": "18.2.10", + "tag": "@ni/nimble-blazor_v18.2.10", + "comments": { + "patch": [ + { + "author": "beachball", + "package": "@ni/nimble-blazor", + "comment": "Bump @ni/nimble-components to v32.3.0", + "commit": "not available" + } + ] + } + }, { "date": "Thu, 26 Sep 2024 18:24:50 GMT", "version": "18.2.9", diff --git a/packages/blazor-workspace/NimbleBlazor/CHANGELOG.md b/packages/blazor-workspace/NimbleBlazor/CHANGELOG.md index 91687012ab..0f468ab22a 100644 --- a/packages/blazor-workspace/NimbleBlazor/CHANGELOG.md +++ b/packages/blazor-workspace/NimbleBlazor/CHANGELOG.md @@ -1,9 +1,17 @@ # Change Log - @ni/nimble-blazor - + +## 18.2.10 + +Wed, 09 Oct 2024 19:24:18 GMT + +### Patches + +- Bump @ni/nimble-components to v32.3.0 + ## 18.2.9 Thu, 26 Sep 2024 18:24:50 GMT diff --git a/packages/blazor-workspace/NimbleBlazor/package.json b/packages/blazor-workspace/NimbleBlazor/package.json index 545a8ed1f9..0475f77852 100644 --- a/packages/blazor-workspace/NimbleBlazor/package.json +++ b/packages/blazor-workspace/NimbleBlazor/package.json @@ -1,6 +1,6 @@ { "name": "@ni/nimble-blazor", - "version": "18.2.9", + "version": "18.2.10", "description": "Blazor components for the NI Nimble Design System", "scripts": { "pack": "cross-env-shell dotnet pack -c Release -p:PackageVersion=$npm_package_version --output ../dist", @@ -25,7 +25,7 @@ "!*" ], "peerDependencies": { - "@ni/nimble-components": "^32.2.10", + "@ni/nimble-components": "^32.3.0", "@ni/nimble-tokens": "^8.3.0", "cross-env": "^7.0.3", "rimraf": "^6.0.0" diff --git a/packages/blazor-workspace/SprightBlazor/CHANGELOG.json b/packages/blazor-workspace/SprightBlazor/CHANGELOG.json index e804e0ce3d..0f27bd3408 100644 --- a/packages/blazor-workspace/SprightBlazor/CHANGELOG.json +++ b/packages/blazor-workspace/SprightBlazor/CHANGELOG.json @@ -1,6 +1,21 @@ { "name": "@ni/spright-blazor", "entries": [ + { + "date": "Wed, 09 Oct 2024 19:24:18 GMT", + "version": "2.1.11", + "tag": "@ni/spright-blazor_v2.1.11", + "comments": { + "patch": [ + { + "author": "beachball", + "package": "@ni/spright-blazor", + "comment": "Bump @ni/spright-components to v4.1.11", + "commit": "not available" + } + ] + } + }, { "date": "Thu, 26 Sep 2024 18:24:50 GMT", "version": "2.1.10", diff --git a/packages/blazor-workspace/SprightBlazor/CHANGELOG.md b/packages/blazor-workspace/SprightBlazor/CHANGELOG.md index 23d8f826e6..bd0074077a 100644 --- a/packages/blazor-workspace/SprightBlazor/CHANGELOG.md +++ b/packages/blazor-workspace/SprightBlazor/CHANGELOG.md @@ -1,9 +1,17 @@ # Change Log - @ni/spright-blazor - + +## 2.1.11 + +Wed, 09 Oct 2024 19:24:18 GMT + +### Patches + +- Bump @ni/spright-components to v4.1.11 + ## 2.1.10 Thu, 26 Sep 2024 18:24:50 GMT diff --git a/packages/blazor-workspace/SprightBlazor/package.json b/packages/blazor-workspace/SprightBlazor/package.json index 9f9e676c20..ba1a075c0a 100644 --- a/packages/blazor-workspace/SprightBlazor/package.json +++ b/packages/blazor-workspace/SprightBlazor/package.json @@ -1,6 +1,6 @@ { "name": "@ni/spright-blazor", - "version": "2.1.10", + "version": "2.1.11", "description": "Blazor components for Spright", "scripts": { "pack": "cross-env-shell dotnet pack -c Release -p:PackageVersion=$npm_package_version --output ../dist", @@ -25,7 +25,7 @@ "!*" ], "peerDependencies": { - "@ni/spright-components": "^4.1.10", + "@ni/spright-components": "^4.1.11", "cross-env": "^7.0.3", "rimraf": "^6.0.0" } diff --git a/packages/nimble-components/CHANGELOG.json b/packages/nimble-components/CHANGELOG.json index 25929bc285..9695564f1d 100644 --- a/packages/nimble-components/CHANGELOG.json +++ b/packages/nimble-components/CHANGELOG.json @@ -1,6 +1,21 @@ { "name": "@ni/nimble-components", "entries": [ + { + "date": "Wed, 09 Oct 2024 19:24:18 GMT", + "version": "32.3.0", + "tag": "@ni/nimble-components_v32.3.0", + "comments": { + "minor": [ + { + "author": "20542556+mollykreis@users.noreply.github.com", + "package": "@ni/nimble-components", + "commit": "8aa7c1ee2a6fae665419b8a8d9c8f8e7e977c424", + "comment": "Implement error states on nimble-radio-group" + } + ] + } + }, { "date": "Tue, 08 Oct 2024 17:39:50 GMT", "version": "32.2.10", diff --git a/packages/nimble-components/CHANGELOG.md b/packages/nimble-components/CHANGELOG.md index 4cfc50f14d..8ed2e4ec42 100644 --- a/packages/nimble-components/CHANGELOG.md +++ b/packages/nimble-components/CHANGELOG.md @@ -1,9 +1,17 @@ # Change Log - @ni/nimble-components - + +## 32.3.0 + +Wed, 09 Oct 2024 19:24:18 GMT + +### Minor changes + +- Implement error states on nimble-radio-group ([ni/nimble@8aa7c1e](https://github.com/ni/nimble/commit/8aa7c1ee2a6fae665419b8a8d9c8f8e7e977c424)) + ## 32.2.10 Thu, 26 Sep 2024 18:24:50 GMT diff --git a/packages/nimble-components/package.json b/packages/nimble-components/package.json index d2fd1f282c..3eb8c9c136 100644 --- a/packages/nimble-components/package.json +++ b/packages/nimble-components/package.json @@ -1,6 +1,6 @@ { "name": "@ni/nimble-components", - "version": "32.2.10", + "version": "32.3.0", "description": "Styled web components for the NI Nimble Design System", "scripts": { "build": "npm run generate-icons && npm run generate-workers && npm run build-components && npm run bundle-components && npm run generate-scss", diff --git a/packages/spright-components/CHANGELOG.json b/packages/spright-components/CHANGELOG.json index 088aada8d7..979eb01455 100644 --- a/packages/spright-components/CHANGELOG.json +++ b/packages/spright-components/CHANGELOG.json @@ -1,6 +1,21 @@ { "name": "@ni/spright-components", "entries": [ + { + "date": "Wed, 09 Oct 2024 19:24:18 GMT", + "version": "4.1.11", + "tag": "@ni/spright-components_v4.1.11", + "comments": { + "patch": [ + { + "author": "beachball", + "package": "@ni/spright-components", + "comment": "Bump @ni/nimble-components to v32.3.0", + "commit": "not available" + } + ] + } + }, { "date": "Thu, 26 Sep 2024 18:24:50 GMT", "version": "4.1.10", diff --git a/packages/spright-components/CHANGELOG.md b/packages/spright-components/CHANGELOG.md index 93051ea01e..a5922d7959 100644 --- a/packages/spright-components/CHANGELOG.md +++ b/packages/spright-components/CHANGELOG.md @@ -1,9 +1,17 @@ # Change Log - @ni/spright-components - + +## 4.1.11 + +Wed, 09 Oct 2024 19:24:18 GMT + +### Patches + +- Bump @ni/nimble-components to v32.3.0 + ## 4.1.10 Thu, 26 Sep 2024 18:24:50 GMT diff --git a/packages/spright-components/package.json b/packages/spright-components/package.json index 2183c65388..8ac7d09163 100644 --- a/packages/spright-components/package.json +++ b/packages/spright-components/package.json @@ -1,6 +1,6 @@ { "name": "@ni/spright-components", - "version": "4.1.10", + "version": "4.1.11", "description": "NI Spright Components", "scripts": { "build": "npm run build-components && npm run bundle-components", @@ -50,7 +50,7 @@ "dependencies": { "@microsoft/fast-element": "^1.12.0", "@microsoft/fast-foundation": "^2.49.6", - "@ni/nimble-components": "^32.2.10", + "@ni/nimble-components": "^32.3.0", "tslib": "^2.2.0" }, "devDependencies": { From 943ce38c3a94ac7e58c7d7c35e43a579be80547a Mon Sep 17 00:00:00 2001 From: Jesse Attas Date: Wed, 9 Oct 2024 16:58:36 -0500 Subject: [PATCH 5/6] Migrate Angular router link tests to use RouterTestingHarness (#2430) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Pull Request ## ๐Ÿคจ Rationale We have several tests that verify Angular router link behavior by clicking links and inspecting router state. These tests exhibit a couple problems: 1. currently they report warnings in the output log like: `[web-server]: 404: /_karma_webpack_/page1?param1=true` 2. In #2387 when we bring in a newer version of Chromium, these cause other tests to fail to execute and report timeouts The root cause of these issues is that the tests are actually trying to navigate the page when the link is clicked. ## ๐Ÿ‘ฉโ€๐Ÿ’ป Implementation ### "with-href" `nimbleRouterLink` tests In researching best practices for writing tests like this I learned that the `RouterTestingModule` we had been using has been deprecated and replaced with a more powerful `RouterTestingHarness`. [This blog and video](https://www.rainerhahnekamp.com/en/how-do-i-test-using-the-routertestingharness/) does a good job of explaining it, much better than [the angular docs](https://angular.dev/api/router/testing/RouterTestingHarness). The basic idea is that the harness sets up a parent component and router which host your component under test. When something tries to navigate the harness captures information about the navigation but doesn't actually navigate the page. The fixes in this PR are to use `RouterTestingHarness` instead of `RouterTestingModule`. This has these side effects: 1. The routes are configured with `provideRouter` instead of `withRoutes` 2. The navigated route started to be relative to the current route, so starting from `/start` and clicking a link to `page` resulted in a URL of `/start/page`. The simplest fix I found for this was to change the starting page to `/`. 3. Some setup code could be deleted and made sync/async instead of fakeAsync. In addition even if the router doesn't navigate the page we are still invoking click handlers on anchors which try to navigate the page. To address that I'm calling `preventDefault()` from those handlers. ### error `routerLink` tests These tests included a `RouterTestingModule` but didn't actually need it or `RouterTestingHarness` so I deleted those imports. ### package.json Added a couple dev scripts that I found useful while running tests locally. We didn't have an obvious way to debug angular tests or to run just the tests for one project and now we do. ## ๐Ÿงช Testing 1. Verified the 404 warnings are no longer printed to the console 5. Verified the tests don't navigate the page in the newer version of Chromium 6. Verified tests still fail with various changes like changing the URL or not clicking the link ## โœ… Checklist - [ ] I have updated the project documentation to reflect my changes or determined no changes are needed. --------- Co-authored-by: Milan Raj --- ...-16d68d67-9ecc-41b3-b7aa-cb52dbd66da4.json | 7 ++++ ...on-router-link-with-href.directive.spec.ts | 38 +++++++++--------- ...nchor-button-router-link.directive.spec.ts | 4 +- ...em-router-link-with-href.directive.spec.ts | 38 +++++++++--------- ...or-menu-item-router-link.directive.spec.ts | 10 ++--- ...ab-router-link-with-href.directive.spec.ts | 38 +++++++++--------- ...e-anchor-tab-router-link.directive.spec.ts | 9 ++--- ...em-router-link-with-href.directive.spec.ts | 38 +++++++++--------- ...or-tree-item-router-link.directive.spec.ts | 4 +- ...or-router-link-with-href.directive.spec.ts | 35 ++++++++--------- ...imble-anchor-router-link.directive.spec.ts | 6 +-- ...em-router-link-with-href.directive.spec.ts | 39 +++++++++---------- ...adcrumb-item-router-link.directive.spec.ts | 9 ++--- ...-anchor-navigation-guard.directive.spec.ts | 4 +- packages/angular-workspace/package.json | 8 +++- 15 files changed, 137 insertions(+), 150 deletions(-) create mode 100644 change/@ni-nimble-angular-16d68d67-9ecc-41b3-b7aa-cb52dbd66da4.json diff --git a/change/@ni-nimble-angular-16d68d67-9ecc-41b3-b7aa-cb52dbd66da4.json b/change/@ni-nimble-angular-16d68d67-9ecc-41b3-b7aa-cb52dbd66da4.json new file mode 100644 index 0000000000..c19e126405 --- /dev/null +++ b/change/@ni-nimble-angular-16d68d67-9ecc-41b3-b7aa-cb52dbd66da4.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "Angular router link test improvements", + "packageName": "@ni/nimble-angular", + "email": "jattasNI@users.noreply.github.com", + "dependentChangeType": "none" +} diff --git a/packages/angular-workspace/nimble-angular/src/directives/anchor-button/tests/nimble-anchor-button-router-link-with-href.directive.spec.ts b/packages/angular-workspace/nimble-angular/src/directives/anchor-button/tests/nimble-anchor-button-router-link-with-href.directive.spec.ts index 92cb9a098f..c5a7741bd3 100644 --- a/packages/angular-workspace/nimble-angular/src/directives/anchor-button/tests/nimble-anchor-button-router-link-with-href.directive.spec.ts +++ b/packages/angular-workspace/nimble-angular/src/directives/anchor-button/tests/nimble-anchor-button-router-link-with-href.directive.spec.ts @@ -1,8 +1,9 @@ import { Component, ElementRef, Sanitizer, SecurityContext, ViewChild } from '@angular/core'; -import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; -import { Router } from '@angular/router'; +import { fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { provideRouter, Router } from '@angular/router'; import { CommonModule, Location } from '@angular/common'; -import { RouterTestingModule } from '@angular/router/testing'; +import { RouterTestingHarness } from '@angular/router/testing'; import { parameterizeSpec } from '@ni/jasmine-parameterized'; import { processUpdates } from '../../../testing/async-helpers'; import { NimbleAnchorButtonModule } from '../nimble-anchor-button.module'; @@ -14,7 +15,6 @@ describe('Nimble anchor button RouterLinkWithHrefDirective', () => { Anchor text - ` }) class TestHostComponent { @@ -25,7 +25,6 @@ describe('Nimble anchor button RouterLinkWithHrefDirective', () => { class BlankComponent { } let anchorButton: AnchorButton; - let fixture: ComponentFixture; let testHostComponent: TestHostComponent; let router: Router; let location: Location; @@ -33,41 +32,40 @@ describe('Nimble anchor button RouterLinkWithHrefDirective', () => { let routerNavigateByUrlSpy: jasmine.Spy; let anchorClickHandlerSpy: jasmine.Spy; let sanitizer: jasmine.SpyObj; + let harness: RouterTestingHarness; - beforeEach(() => { + beforeEach(async () => { sanitizer = jasmine.createSpyObj('Sanitizer', ['sanitize']); sanitizer.sanitize.and.callFake((_, value: string) => value); TestBed.configureTestingModule({ declarations: [TestHostComponent, BlankComponent], - imports: [NimbleAnchorButtonModule, + imports: [ + NimbleAnchorButtonModule, CommonModule, - RouterTestingModule.withRoutes([ - { path: '', redirectTo: '/start', pathMatch: 'full' }, - { path: 'page1', component: BlankComponent }, - { path: 'start', component: TestHostComponent } - ], { useHash: true }) ], providers: [ - { provide: Sanitizer, useValue: sanitizer } + { provide: Sanitizer, useValue: sanitizer }, + provideRouter([ + { path: 'page1', component: BlankComponent }, + { path: '', component: TestHostComponent } + ]), ] }); + harness = await RouterTestingHarness.create(''); }); - beforeEach(fakeAsync(() => { + beforeEach(() => { router = TestBed.inject(Router); location = TestBed.inject(Location); - fixture = TestBed.createComponent(TestHostComponent); - testHostComponent = fixture.componentInstance; + testHostComponent = harness.fixture.debugElement.query(By.directive(TestHostComponent)).componentInstance as TestHostComponent; anchorButton = testHostComponent.anchor.nativeElement; - fixture.detectChanges(); - tick(); processUpdates(); innerAnchor = anchorButton!.shadowRoot!.querySelector('a')!; routerNavigateByUrlSpy = spyOn(router, 'navigateByUrl').and.callThrough(); - anchorClickHandlerSpy = jasmine.createSpy('click'); + anchorClickHandlerSpy = jasmine.createSpy('click').and.callFake((event: Event) => event.preventDefault()); innerAnchor!.addEventListener('click', anchorClickHandlerSpy); - })); + }); afterEach(() => { processUpdates(); diff --git a/packages/angular-workspace/nimble-angular/src/directives/anchor-button/tests/nimble-anchor-button-router-link.directive.spec.ts b/packages/angular-workspace/nimble-angular/src/directives/anchor-button/tests/nimble-anchor-button-router-link.directive.spec.ts index d64bb909d2..06d8232b2e 100644 --- a/packages/angular-workspace/nimble-angular/src/directives/anchor-button/tests/nimble-anchor-button-router-link.directive.spec.ts +++ b/packages/angular-workspace/nimble-angular/src/directives/anchor-button/tests/nimble-anchor-button-router-link.directive.spec.ts @@ -1,7 +1,6 @@ import { Component } from '@angular/core'; import { TestBed } from '@angular/core/testing'; import { CommonModule } from '@angular/common'; -import { RouterTestingModule } from '@angular/router/testing'; import { processUpdates } from '../../../testing/async-helpers'; import { NimbleAnchorButtonModule } from '../nimble-anchor-button.module'; @@ -23,8 +22,7 @@ describe('Nimble anchor button RouterLinkDirective', () => { imports: [ NimbleAnchorButtonModule, CommonModule, - RouterTestingModule.withRoutes([{ path: '', component: TestHostComponent, pathMatch: 'full' }], { useHash: true }) - ] + ], }); }); diff --git a/packages/angular-workspace/nimble-angular/src/directives/anchor-menu-item/tests/nimble-anchor-menu-item-router-link-with-href.directive.spec.ts b/packages/angular-workspace/nimble-angular/src/directives/anchor-menu-item/tests/nimble-anchor-menu-item-router-link-with-href.directive.spec.ts index e80ad56781..e43ef5447f 100644 --- a/packages/angular-workspace/nimble-angular/src/directives/anchor-menu-item/tests/nimble-anchor-menu-item-router-link-with-href.directive.spec.ts +++ b/packages/angular-workspace/nimble-angular/src/directives/anchor-menu-item/tests/nimble-anchor-menu-item-router-link-with-href.directive.spec.ts @@ -1,8 +1,9 @@ import { Component, ElementRef, Sanitizer, SecurityContext, ViewChild } from '@angular/core'; -import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; -import { Router } from '@angular/router'; +import { fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { provideRouter, Router } from '@angular/router'; import { CommonModule, Location } from '@angular/common'; -import { RouterTestingModule } from '@angular/router/testing'; +import { RouterTestingHarness } from '@angular/router/testing'; import { parameterizeSpec } from '@ni/jasmine-parameterized'; import { processUpdates } from '../../../testing/async-helpers'; import { NimbleAnchorMenuItemModule } from '../nimble-anchor-menu-item.module'; @@ -14,7 +15,6 @@ describe('Nimble anchor menu item RouterLinkWithHrefDirective', () => { Anchor text - ` }) class TestHostComponent { @@ -25,7 +25,6 @@ describe('Nimble anchor menu item RouterLinkWithHrefDirective', () => { class BlankComponent { } let menuItem: AnchorMenuItem; - let fixture: ComponentFixture; let testHostComponent: TestHostComponent; let router: Router; let location: Location; @@ -33,41 +32,40 @@ describe('Nimble anchor menu item RouterLinkWithHrefDirective', () => { let routerNavigateByUrlSpy: jasmine.Spy; let anchorClickHandlerSpy: jasmine.Spy; let sanitizer: jasmine.SpyObj; + let harness: RouterTestingHarness; - beforeEach(() => { + beforeEach(async () => { sanitizer = jasmine.createSpyObj('Sanitizer', ['sanitize']); sanitizer.sanitize.and.callFake((_, value: string) => value); TestBed.configureTestingModule({ declarations: [TestHostComponent, BlankComponent], - imports: [NimbleAnchorMenuItemModule, + imports: [ + NimbleAnchorMenuItemModule, CommonModule, - RouterTestingModule.withRoutes([ - { path: '', redirectTo: '/start', pathMatch: 'full' }, - { path: 'page1', component: BlankComponent }, - { path: 'start', component: TestHostComponent } - ], { useHash: true }) ], providers: [ - { provide: Sanitizer, useValue: sanitizer } + { provide: Sanitizer, useValue: sanitizer }, + provideRouter([ + { path: 'page1', component: BlankComponent }, + { path: '', component: TestHostComponent } + ]), ] }); + harness = await RouterTestingHarness.create(''); }); - beforeEach(fakeAsync(() => { + beforeEach(() => { router = TestBed.inject(Router); location = TestBed.inject(Location); - fixture = TestBed.createComponent(TestHostComponent); - testHostComponent = fixture.componentInstance; + testHostComponent = harness.fixture.debugElement.query(By.directive(TestHostComponent)).componentInstance as TestHostComponent; menuItem = testHostComponent.menuItem.nativeElement; - fixture.detectChanges(); - tick(); processUpdates(); innerAnchor = menuItem!.shadowRoot!.querySelector('a')!; routerNavigateByUrlSpy = spyOn(router, 'navigateByUrl').and.callThrough(); - anchorClickHandlerSpy = jasmine.createSpy('click'); + anchorClickHandlerSpy = jasmine.createSpy('click').and.callFake((event: Event) => event.preventDefault()); innerAnchor!.addEventListener('click', anchorClickHandlerSpy); - })); + }); afterEach(() => { processUpdates(); diff --git a/packages/angular-workspace/nimble-angular/src/directives/anchor-menu-item/tests/nimble-anchor-menu-item-router-link.directive.spec.ts b/packages/angular-workspace/nimble-angular/src/directives/anchor-menu-item/tests/nimble-anchor-menu-item-router-link.directive.spec.ts index 0c2b345e36..9cf045a924 100644 --- a/packages/angular-workspace/nimble-angular/src/directives/anchor-menu-item/tests/nimble-anchor-menu-item-router-link.directive.spec.ts +++ b/packages/angular-workspace/nimble-angular/src/directives/anchor-menu-item/tests/nimble-anchor-menu-item-router-link.directive.spec.ts @@ -1,7 +1,6 @@ import { Component } from '@angular/core'; import { TestBed } from '@angular/core/testing'; import { CommonModule } from '@angular/common'; -import { RouterTestingModule } from '@angular/router/testing'; import { processUpdates } from '../../../testing/async-helpers'; import { NimbleAnchorMenuItemModule } from '../nimble-anchor-menu-item.module'; @@ -20,11 +19,10 @@ describe('Nimble anchor menu item RouterLinkDirective', () => { beforeEach(() => { TestBed.configureTestingModule({ declarations: [TestHostComponent], - imports: [NimbleAnchorMenuItemModule, - CommonModule, - RouterTestingModule.withRoutes([{ path: '', component: TestHostComponent, pathMatch: 'full' } - ], { useHash: true }) - ] + imports: [ + NimbleAnchorMenuItemModule, + CommonModule + ], }); }); diff --git a/packages/angular-workspace/nimble-angular/src/directives/anchor-tab/tests/nimble-anchor-tab-router-link-with-href.directive.spec.ts b/packages/angular-workspace/nimble-angular/src/directives/anchor-tab/tests/nimble-anchor-tab-router-link-with-href.directive.spec.ts index d8bebb9743..008aa71d81 100644 --- a/packages/angular-workspace/nimble-angular/src/directives/anchor-tab/tests/nimble-anchor-tab-router-link-with-href.directive.spec.ts +++ b/packages/angular-workspace/nimble-angular/src/directives/anchor-tab/tests/nimble-anchor-tab-router-link-with-href.directive.spec.ts @@ -1,8 +1,9 @@ import { Component, ElementRef, Sanitizer, SecurityContext, ViewChild } from '@angular/core'; -import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; -import { Router } from '@angular/router'; +import { fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { provideRouter, Router } from '@angular/router'; import { CommonModule, Location } from '@angular/common'; -import { RouterTestingModule } from '@angular/router/testing'; +import { By } from '@angular/platform-browser'; +import { RouterTestingHarness } from '@angular/router/testing'; import { parameterizeSpec } from '@ni/jasmine-parameterized'; import { processUpdates } from '../../../testing/async-helpers'; import { NimbleAnchorTabModule } from '../nimble-anchor-tab.module'; @@ -14,7 +15,6 @@ describe('Nimble anchor tab RouterLinkWithHrefDirective', () => { Anchor text - ` }) class TestHostComponent { @@ -25,7 +25,6 @@ describe('Nimble anchor tab RouterLinkWithHrefDirective', () => { class BlankComponent { } let anchorTab: AnchorTab; - let fixture: ComponentFixture; let testHostComponent: TestHostComponent; let router: Router; let location: Location; @@ -33,41 +32,40 @@ describe('Nimble anchor tab RouterLinkWithHrefDirective', () => { let routerNavigateByUrlSpy: jasmine.Spy; let anchorClickHandlerSpy: jasmine.Spy; let sanitizer: jasmine.SpyObj; + let harness: RouterTestingHarness; - beforeEach(() => { + beforeEach(async () => { sanitizer = jasmine.createSpyObj('Sanitizer', ['sanitize']); sanitizer.sanitize.and.callFake((_, value: string) => value); TestBed.configureTestingModule({ declarations: [TestHostComponent, BlankComponent], - imports: [NimbleAnchorTabModule, + imports: [ + NimbleAnchorTabModule, CommonModule, - RouterTestingModule.withRoutes([ - { path: '', redirectTo: '/start', pathMatch: 'full' }, - { path: 'page1', component: BlankComponent }, - { path: 'start', component: TestHostComponent } - ], { useHash: true }) ], providers: [ - { provide: Sanitizer, useValue: sanitizer } + { provide: Sanitizer, useValue: sanitizer }, + provideRouter([ + { path: 'page1', component: BlankComponent }, + { path: '', component: TestHostComponent } + ]), ] }); + harness = await RouterTestingHarness.create(''); }); - beforeEach(fakeAsync(() => { + beforeEach(() => { router = TestBed.inject(Router); location = TestBed.inject(Location); - fixture = TestBed.createComponent(TestHostComponent); - testHostComponent = fixture.componentInstance; + testHostComponent = harness.fixture.debugElement.query(By.directive(TestHostComponent)).componentInstance as TestHostComponent; anchorTab = testHostComponent.anchorTab.nativeElement; - fixture.detectChanges(); - tick(); processUpdates(); innerAnchor = anchorTab!.shadowRoot!.querySelector('a')!; routerNavigateByUrlSpy = spyOn(router, 'navigateByUrl').and.callThrough(); - anchorClickHandlerSpy = jasmine.createSpy('click'); + anchorClickHandlerSpy = jasmine.createSpy('click').and.callFake((event: Event) => event.preventDefault()); innerAnchor!.addEventListener('click', anchorClickHandlerSpy); - })); + }); afterEach(() => { processUpdates(); diff --git a/packages/angular-workspace/nimble-angular/src/directives/anchor-tab/tests/nimble-anchor-tab-router-link.directive.spec.ts b/packages/angular-workspace/nimble-angular/src/directives/anchor-tab/tests/nimble-anchor-tab-router-link.directive.spec.ts index 85576f3d97..e4a25a5bd8 100644 --- a/packages/angular-workspace/nimble-angular/src/directives/anchor-tab/tests/nimble-anchor-tab-router-link.directive.spec.ts +++ b/packages/angular-workspace/nimble-angular/src/directives/anchor-tab/tests/nimble-anchor-tab-router-link.directive.spec.ts @@ -1,7 +1,6 @@ import { Component } from '@angular/core'; import { TestBed } from '@angular/core/testing'; import { CommonModule } from '@angular/common'; -import { RouterTestingModule } from '@angular/router/testing'; import { processUpdates } from '../../../testing/async-helpers'; import { NimbleAnchorTabModule } from '../nimble-anchor-tab.module'; @@ -20,12 +19,10 @@ describe('Nimble anchor tab RouterLinkDirective', () => { beforeEach(() => { TestBed.configureTestingModule({ declarations: [TestHostComponent], - imports: [NimbleAnchorTabModule, + imports: [ + NimbleAnchorTabModule, CommonModule, - RouterTestingModule.withRoutes([ - { path: '', component: TestHostComponent, pathMatch: 'full' } - ], { useHash: true }) - ] + ], }); }); diff --git a/packages/angular-workspace/nimble-angular/src/directives/anchor-tree-item/tests/nimble-anchor-tree-item-router-link-with-href.directive.spec.ts b/packages/angular-workspace/nimble-angular/src/directives/anchor-tree-item/tests/nimble-anchor-tree-item-router-link-with-href.directive.spec.ts index b1a2737fcb..f0b25f0291 100644 --- a/packages/angular-workspace/nimble-angular/src/directives/anchor-tree-item/tests/nimble-anchor-tree-item-router-link-with-href.directive.spec.ts +++ b/packages/angular-workspace/nimble-angular/src/directives/anchor-tree-item/tests/nimble-anchor-tree-item-router-link-with-href.directive.spec.ts @@ -1,8 +1,9 @@ import { Component, ElementRef, Sanitizer, SecurityContext, ViewChild } from '@angular/core'; -import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; -import { Router } from '@angular/router'; +import { fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { provideRouter, Router } from '@angular/router'; import { CommonModule, Location } from '@angular/common'; -import { RouterTestingModule } from '@angular/router/testing'; +import { RouterTestingHarness } from '@angular/router/testing'; import { parameterizeSpec } from '@ni/jasmine-parameterized'; import { processUpdates } from '../../../testing/async-helpers'; import { NimbleAnchorTreeItemModule } from '../nimble-anchor-tree-item.module'; @@ -14,7 +15,6 @@ describe('Nimble anchor tree item RouterLinkWithHrefDirective', () => { Anchor text - ` }) class TestHostComponent { @@ -25,7 +25,6 @@ describe('Nimble anchor tree item RouterLinkWithHrefDirective', () => { class BlankComponent { } let anchorTreeItem: AnchorTreeItem; - let fixture: ComponentFixture; let testHostComponent: TestHostComponent; let router: Router; let location: Location; @@ -33,41 +32,40 @@ describe('Nimble anchor tree item RouterLinkWithHrefDirective', () => { let routerNavigateByUrlSpy: jasmine.Spy; let anchorClickHandlerSpy: jasmine.Spy; let sanitizer: jasmine.SpyObj; + let harness: RouterTestingHarness; - beforeEach(() => { + beforeEach(async () => { sanitizer = jasmine.createSpyObj('Sanitizer', ['sanitize']); sanitizer.sanitize.and.callFake((_, value: string) => value); TestBed.configureTestingModule({ declarations: [TestHostComponent, BlankComponent], - imports: [NimbleAnchorTreeItemModule, + imports: [ + NimbleAnchorTreeItemModule, CommonModule, - RouterTestingModule.withRoutes([ - { path: '', redirectTo: '/start', pathMatch: 'full' }, - { path: 'page1', component: BlankComponent }, - { path: 'start', component: TestHostComponent } - ], { useHash: true }) ], providers: [ - { provide: Sanitizer, useValue: sanitizer } + { provide: Sanitizer, useValue: sanitizer }, + provideRouter([ + { path: 'page1', component: BlankComponent }, + { path: '', component: TestHostComponent } + ]), ] }); + harness = await RouterTestingHarness.create(''); }); - beforeEach(fakeAsync(() => { + beforeEach(() => { router = TestBed.inject(Router); location = TestBed.inject(Location); - fixture = TestBed.createComponent(TestHostComponent); - testHostComponent = fixture.componentInstance; + testHostComponent = harness.fixture.debugElement.query(By.directive(TestHostComponent)).componentInstance as TestHostComponent; anchorTreeItem = testHostComponent.treeItem.nativeElement; - fixture.detectChanges(); - tick(); processUpdates(); innerAnchor = anchorTreeItem!.shadowRoot!.querySelector('a')!; routerNavigateByUrlSpy = spyOn(router, 'navigateByUrl').and.callThrough(); - anchorClickHandlerSpy = jasmine.createSpy('click'); + anchorClickHandlerSpy = jasmine.createSpy('click').and.callFake((event: Event) => event.preventDefault()); innerAnchor!.addEventListener('click', anchorClickHandlerSpy); - })); + }); afterEach(() => { processUpdates(); diff --git a/packages/angular-workspace/nimble-angular/src/directives/anchor-tree-item/tests/nimble-anchor-tree-item-router-link.directive.spec.ts b/packages/angular-workspace/nimble-angular/src/directives/anchor-tree-item/tests/nimble-anchor-tree-item-router-link.directive.spec.ts index 17fd45c5e0..4abd871605 100644 --- a/packages/angular-workspace/nimble-angular/src/directives/anchor-tree-item/tests/nimble-anchor-tree-item-router-link.directive.spec.ts +++ b/packages/angular-workspace/nimble-angular/src/directives/anchor-tree-item/tests/nimble-anchor-tree-item-router-link.directive.spec.ts @@ -1,7 +1,6 @@ import { Component } from '@angular/core'; import { TestBed } from '@angular/core/testing'; import { CommonModule } from '@angular/common'; -import { RouterTestingModule } from '@angular/router/testing'; import { processUpdates } from '../../../testing/async-helpers'; import { NimbleAnchorTreeItemModule } from '../nimble-anchor-tree-item.module'; @@ -23,8 +22,7 @@ describe('Nimble anchor tree item RouterLinkDirective', () => { imports: [ NimbleAnchorTreeItemModule, CommonModule, - RouterTestingModule.withRoutes([{ path: '', component: TestHostComponent, pathMatch: 'full' }], { useHash: true }) - ] + ], }); }); diff --git a/packages/angular-workspace/nimble-angular/src/directives/anchor/tests/nimble-anchor-router-link-with-href.directive.spec.ts b/packages/angular-workspace/nimble-angular/src/directives/anchor/tests/nimble-anchor-router-link-with-href.directive.spec.ts index 6895081b77..766a67a4c8 100644 --- a/packages/angular-workspace/nimble-angular/src/directives/anchor/tests/nimble-anchor-router-link-with-href.directive.spec.ts +++ b/packages/angular-workspace/nimble-angular/src/directives/anchor/tests/nimble-anchor-router-link-with-href.directive.spec.ts @@ -1,8 +1,9 @@ import { Component, ElementRef, Sanitizer, SecurityContext, ViewChild } from '@angular/core'; -import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; -import { Router } from '@angular/router'; +import { fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { provideRouter, Router } from '@angular/router'; import { CommonModule, Location } from '@angular/common'; -import { RouterTestingModule } from '@angular/router/testing'; +import { RouterTestingHarness } from '@angular/router/testing'; import { parameterizeSpec } from '@ni/jasmine-parameterized'; import { processUpdates } from '../../../testing/async-helpers'; import { NimbleAnchorModule } from '../nimble-anchor.module'; @@ -14,7 +15,6 @@ describe('Nimble anchor RouterLinkWithHrefDirective', () => { Anchor text - ` }) class TestHostComponent { @@ -25,7 +25,6 @@ describe('Nimble anchor RouterLinkWithHrefDirective', () => { class BlankComponent { } let anchor: Anchor; - let fixture: ComponentFixture; let testHostComponent: TestHostComponent; let router: Router; let location: Location; @@ -33,8 +32,9 @@ describe('Nimble anchor RouterLinkWithHrefDirective', () => { let routerNavigateByUrlSpy: jasmine.Spy; let anchorClickHandlerSpy: jasmine.Spy; let sanitizer: jasmine.SpyObj; + let harness: RouterTestingHarness; - beforeEach(() => { + beforeEach(async () => { sanitizer = jasmine.createSpyObj('Sanitizer', ['sanitize']); sanitizer.sanitize.and.callFake((_, value: string) => value); @@ -42,32 +42,29 @@ describe('Nimble anchor RouterLinkWithHrefDirective', () => { declarations: [TestHostComponent, BlankComponent], imports: [NimbleAnchorModule, CommonModule, - RouterTestingModule.withRoutes([ - { path: '', redirectTo: '/start', pathMatch: 'full' }, - { path: 'page1', component: BlankComponent }, - { path: 'start', component: TestHostComponent } - ], { useHash: true }) ], providers: [ - { provide: Sanitizer, useValue: sanitizer } + { provide: Sanitizer, useValue: sanitizer }, + provideRouter([ + { path: 'page1', component: BlankComponent }, + { path: '', component: TestHostComponent } + ]), ] }); + harness = await RouterTestingHarness.create(''); }); - beforeEach(fakeAsync(() => { + beforeEach(() => { router = TestBed.inject(Router); location = TestBed.inject(Location); - fixture = TestBed.createComponent(TestHostComponent); - testHostComponent = fixture.componentInstance; + testHostComponent = harness.fixture.debugElement.query(By.directive(TestHostComponent)).componentInstance as TestHostComponent; anchor = testHostComponent.anchor.nativeElement; - fixture.detectChanges(); - tick(); processUpdates(); innerAnchor = anchor!.shadowRoot!.querySelector('a')!; routerNavigateByUrlSpy = spyOn(router, 'navigateByUrl').and.callThrough(); - anchorClickHandlerSpy = jasmine.createSpy('click'); + anchorClickHandlerSpy = jasmine.createSpy('click').and.callFake((event: Event) => event.preventDefault()); innerAnchor!.addEventListener('click', anchorClickHandlerSpy); - })); + }); afterEach(() => { processUpdates(); diff --git a/packages/angular-workspace/nimble-angular/src/directives/anchor/tests/nimble-anchor-router-link.directive.spec.ts b/packages/angular-workspace/nimble-angular/src/directives/anchor/tests/nimble-anchor-router-link.directive.spec.ts index e189e98291..72255fc843 100644 --- a/packages/angular-workspace/nimble-angular/src/directives/anchor/tests/nimble-anchor-router-link.directive.spec.ts +++ b/packages/angular-workspace/nimble-angular/src/directives/anchor/tests/nimble-anchor-router-link.directive.spec.ts @@ -1,7 +1,6 @@ import { Component } from '@angular/core'; import { TestBed } from '@angular/core/testing'; import { CommonModule } from '@angular/common'; -import { RouterTestingModule } from '@angular/router/testing'; import { processUpdates } from '../../../testing/async-helpers'; import { NimbleAnchorModule } from '../nimble-anchor.module'; @@ -20,10 +19,9 @@ describe('Nimble anchor RouterLinkDirective', () => { beforeEach(() => { TestBed.configureTestingModule({ declarations: [TestHostComponent], - imports: [NimbleAnchorModule, + imports: [ + NimbleAnchorModule, CommonModule, - RouterTestingModule.withRoutes([{ path: '', component: TestHostComponent, pathMatch: 'full' } - ], { useHash: true }) ] }); }); diff --git a/packages/angular-workspace/nimble-angular/src/directives/breadcrumb-item/tests/nimble-breadcrumb-item-router-link-with-href.directive.spec.ts b/packages/angular-workspace/nimble-angular/src/directives/breadcrumb-item/tests/nimble-breadcrumb-item-router-link-with-href.directive.spec.ts index 7ab306df0f..ecd1990b50 100644 --- a/packages/angular-workspace/nimble-angular/src/directives/breadcrumb-item/tests/nimble-breadcrumb-item-router-link-with-href.directive.spec.ts +++ b/packages/angular-workspace/nimble-angular/src/directives/breadcrumb-item/tests/nimble-breadcrumb-item-router-link-with-href.directive.spec.ts @@ -1,8 +1,9 @@ import { Component, ElementRef, Sanitizer, SecurityContext, ViewChild } from '@angular/core'; -import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; -import { Router } from '@angular/router'; +import { fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { provideRouter, Router } from '@angular/router'; import { CommonModule, Location } from '@angular/common'; -import { RouterTestingModule } from '@angular/router/testing'; +import { RouterTestingHarness } from '@angular/router/testing'; import { parameterizeSpec } from '@ni/jasmine-parameterized'; import { processUpdates } from '../../../testing/async-helpers'; import { NimbleBreadcrumbModule } from '../../breadcrumb/nimble-breadcrumb.module'; @@ -17,7 +18,6 @@ describe('Nimble breadcrumb item RouterLinkWithHrefDirective', () => { Breadcrumb Text - ` }) class TestHostComponent { @@ -28,7 +28,6 @@ describe('Nimble breadcrumb item RouterLinkWithHrefDirective', () => { class BlankComponent { } let breadcrumbItem1: BreadcrumbItem; - let fixture: ComponentFixture; let testHostComponent: TestHostComponent; let router: Router; let location: Location; @@ -38,44 +37,44 @@ describe('Nimble breadcrumb item RouterLinkWithHrefDirective', () => { let anchorClickHandlerSpy: jasmine.Spy; let separatorClickHandlerSpy: jasmine.Spy; let sanitizer: jasmine.SpyObj; + let harness: RouterTestingHarness; - beforeEach(() => { + beforeEach(async () => { sanitizer = jasmine.createSpyObj('Sanitizer', ['sanitize']); sanitizer.sanitize.and.callFake((_, value: string) => value); TestBed.configureTestingModule({ declarations: [TestHostComponent, BlankComponent], - imports: [NimbleBreadcrumbModule, NimbleBreadcrumbItemModule, + imports: [ + NimbleBreadcrumbModule, + NimbleBreadcrumbItemModule, CommonModule, - RouterTestingModule.withRoutes([ - { path: '', redirectTo: '/start', pathMatch: 'full' }, - { path: 'page1', component: BlankComponent }, - { path: 'start', component: TestHostComponent } - ], { useHash: true }) ], providers: [ - { provide: Sanitizer, useValue: sanitizer } + { provide: Sanitizer, useValue: sanitizer }, + provideRouter([ + { path: 'page1', component: BlankComponent }, + { path: '', component: TestHostComponent } + ]) ] }); + harness = await RouterTestingHarness.create(''); }); - beforeEach(fakeAsync(() => { + beforeEach(() => { router = TestBed.inject(Router); location = TestBed.inject(Location); - fixture = TestBed.createComponent(TestHostComponent); - testHostComponent = fixture.componentInstance; + testHostComponent = harness.fixture.debugElement.query(By.directive(TestHostComponent)).componentInstance as TestHostComponent; breadcrumbItem1 = testHostComponent.breadcrumbItem1.nativeElement; - fixture.detectChanges(); - tick(); processUpdates(); anchor = breadcrumbItem1!.shadowRoot!.querySelector('a')!; separator = breadcrumbItem1!.shadowRoot!.querySelector('.separator')!; routerNavigateByUrlSpy = spyOn(router, 'navigateByUrl').and.callThrough(); - anchorClickHandlerSpy = jasmine.createSpy('click'); + anchorClickHandlerSpy = jasmine.createSpy('click').and.callFake((event: Event) => event.preventDefault()); separatorClickHandlerSpy = jasmine.createSpy('click'); anchor.addEventListener('click', anchorClickHandlerSpy); separator.addEventListener('click', separatorClickHandlerSpy); - })); + }); afterEach(() => { processUpdates(); diff --git a/packages/angular-workspace/nimble-angular/src/directives/breadcrumb-item/tests/nimble-breadcrumb-item-router-link.directive.spec.ts b/packages/angular-workspace/nimble-angular/src/directives/breadcrumb-item/tests/nimble-breadcrumb-item-router-link.directive.spec.ts index 4402ece081..b5882b46f2 100644 --- a/packages/angular-workspace/nimble-angular/src/directives/breadcrumb-item/tests/nimble-breadcrumb-item-router-link.directive.spec.ts +++ b/packages/angular-workspace/nimble-angular/src/directives/breadcrumb-item/tests/nimble-breadcrumb-item-router-link.directive.spec.ts @@ -1,7 +1,6 @@ import { Component } from '@angular/core'; import { TestBed } from '@angular/core/testing'; import { CommonModule } from '@angular/common'; -import { RouterTestingModule } from '@angular/router/testing'; import { processUpdates } from '../../../testing/async-helpers'; import { NimbleBreadcrumbModule } from '../../breadcrumb/nimble-breadcrumb.module'; import { NimbleBreadcrumbItemModule } from '../nimble-breadcrumb-item.module'; @@ -23,11 +22,11 @@ describe('Nimble breadcrumb item RouterLinkDirective', () => { beforeEach(() => { TestBed.configureTestingModule({ declarations: [TestHostComponent], - imports: [NimbleBreadcrumbModule, NimbleBreadcrumbItemModule, + imports: [ + NimbleBreadcrumbModule, + NimbleBreadcrumbItemModule, CommonModule, - RouterTestingModule.withRoutes([{ path: '', component: TestHostComponent, pathMatch: 'full' } - ], { useHash: true }) - ] + ], }); }); diff --git a/packages/angular-workspace/nimble-angular/table-column/anchor/tests/nimble-table-column-anchor-navigation-guard.directive.spec.ts b/packages/angular-workspace/nimble-angular/table-column/anchor/tests/nimble-table-column-anchor-navigation-guard.directive.spec.ts index 1756358247..a52ed3b7fd 100644 --- a/packages/angular-workspace/nimble-angular/table-column/anchor/tests/nimble-table-column-anchor-navigation-guard.directive.spec.ts +++ b/packages/angular-workspace/nimble-angular/table-column/anchor/tests/nimble-table-column-anchor-navigation-guard.directive.spec.ts @@ -76,7 +76,7 @@ describe('Nimble anchor table column navigation guard', () => { const pageObject = new TablePageObject(testHostComponent.table.nativeElement); anchor = pageObject.getRenderedCellAnchor(0, 0); innerAnchor = anchor!.shadowRoot!.querySelector('a')!; - anchorClickHandlerSpy = jasmine.createSpy('click'); + anchorClickHandlerSpy = jasmine.createSpy('click').and.callFake((event: Event) => event.preventDefault()); innerAnchor!.addEventListener('click', anchorClickHandlerSpy); onClickSpy = spyOn(testHostComponent, 'onClick'); @@ -127,7 +127,7 @@ describe('Nimble anchor table column navigation guard', () => { const pageObject = new TablePageObject(testHostComponent.table.nativeElement); anchor = pageObject.getRenderedCellAnchor(0, 0); innerAnchor = anchor!.shadowRoot!.querySelector('a')!; - anchorClickHandlerSpy = jasmine.createSpy('click'); + anchorClickHandlerSpy = jasmine.createSpy('click').and.callFake((event: Event) => event.preventDefault()); innerAnchor!.addEventListener('click', anchorClickHandlerSpy); onClickSpy = spyOn(testHostComponent, 'onClick'); diff --git a/packages/angular-workspace/package.json b/packages/angular-workspace/package.json index c8350d58cb..2862b6817e 100644 --- a/packages/angular-workspace/package.json +++ b/packages/angular-workspace/package.json @@ -7,9 +7,7 @@ "start": "ng serve", "build": "npm run build:nimble && npm run build:spright && npm run build:application", "build:nimble": "npm run generate-icons && ng build @ni/nimble-angular", - "watch:nimble": "npm run generate-icons && ng build @ni/nimble-angular --watch", "build:spright": "ng build @ni/spright-angular", - "watch:spright": "ng build @ni/spright-angular --watch", "build:application": "ng build example-client-app", "generate-icons": "npm run generate-icons:bundle && npm run generate-icons:run", "generate-icons:bundle": "rollup --bundleConfigAsCjs --config nimble-angular/build/generate-icons/rollup.config.js", @@ -20,7 +18,13 @@ "pack:application": "cd dist/example-client-app && npm pack", "performance": "lhci autorun", "watch": "ng build --watch --configuration development", + "watch-nimble": "npm run generate-icons && ng build @ni/nimble-angular --watch", + "watch-spright": "ng build @ni/spright-angular --watch", + "tdd-nimble": "ng test @ni/nimble-angular --browsers=Chrome", + "tdd-spright": "ng test @ni/spright-angular --browsers=Chrome", "test": "ng test --watch=false", + "test-nimble": "ng test @ni/nimble-angular --watch=false", + "test-spright": "ng test @ni/spright-angular --watch=false", "lint": "ng lint", "format": "ng lint --fix" }, From 29b790b9e5ee15182ee9efed772abef3e3c9b15f Mon Sep 17 00:00:00 2001 From: rajsite Date: Wed, 9 Oct 2024 17:13:54 -0500 Subject: [PATCH 6/6] applying package updates [skip ci] --- ...ular-16d68d67-9ecc-41b3-b7aa-cb52dbd66da4.json | 7 ------- .../nimble-angular/CHANGELOG.json | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 7 deletions(-) delete mode 100644 change/@ni-nimble-angular-16d68d67-9ecc-41b3-b7aa-cb52dbd66da4.json diff --git a/change/@ni-nimble-angular-16d68d67-9ecc-41b3-b7aa-cb52dbd66da4.json b/change/@ni-nimble-angular-16d68d67-9ecc-41b3-b7aa-cb52dbd66da4.json deleted file mode 100644 index c19e126405..0000000000 --- a/change/@ni-nimble-angular-16d68d67-9ecc-41b3-b7aa-cb52dbd66da4.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "none", - "comment": "Angular router link test improvements", - "packageName": "@ni/nimble-angular", - "email": "jattasNI@users.noreply.github.com", - "dependentChangeType": "none" -} diff --git a/packages/angular-workspace/nimble-angular/CHANGELOG.json b/packages/angular-workspace/nimble-angular/CHANGELOG.json index 787863f1ba..dcb7ab8737 100644 --- a/packages/angular-workspace/nimble-angular/CHANGELOG.json +++ b/packages/angular-workspace/nimble-angular/CHANGELOG.json @@ -1,6 +1,21 @@ { "name": "@ni/nimble-angular", "entries": [ + { + "date": "Wed, 09 Oct 2024 22:13:53 GMT", + "version": "28.2.10", + "tag": "@ni/nimble-angular_v28.2.10", + "comments": { + "none": [ + { + "author": "jattasNI@users.noreply.github.com", + "package": "@ni/nimble-angular", + "commit": "943ce38c3a94ac7e58c7d7c35e43a579be80547a", + "comment": "Angular router link test improvements" + } + ] + } + }, { "date": "Wed, 09 Oct 2024 19:24:18 GMT", "version": "28.2.10",