From 94f07d9ed00b943fce25c63cc493363a1a7cfdc9 Mon Sep 17 00:00:00 2001 From: mollykreis <20542556+mollykreis@users.noreply.github.com> Date: Thu, 25 Apr 2024 16:12:24 -0500 Subject: [PATCH 01/13] Create empty mapping element and support it in the icon column --- .../src/mapping/empty/index.ts | 28 ++++ .../empty/tests/mapping-empty.react.tsx | 4 + .../mapping/empty/tests/mapping-empty.spec.ts | 13 ++ .../empty/tests/mapping-empty.stories.ts | 36 +++++ .../enum-base/models/mapping-empty-config.ts | 6 + .../src/table-column/icon/cell-view/index.ts | 4 + .../icon/group-header-view/index.ts | 3 + .../src/table-column/icon/index.ts | 5 + .../models/table-column-icon-validator.ts | 4 +- .../tests/table-column-icon-matrix.stories.ts | 10 +- .../icon/tests/table-column-icon.mdx | 6 + .../icon/tests/table-column-icon.spec.ts | 139 ++++++++++++++---- 12 files changed, 230 insertions(+), 28 deletions(-) create mode 100644 packages/nimble-components/src/mapping/empty/index.ts create mode 100644 packages/nimble-components/src/mapping/empty/tests/mapping-empty.react.tsx create mode 100644 packages/nimble-components/src/mapping/empty/tests/mapping-empty.spec.ts create mode 100644 packages/nimble-components/src/mapping/empty/tests/mapping-empty.stories.ts create mode 100644 packages/nimble-components/src/table-column/enum-base/models/mapping-empty-config.ts diff --git a/packages/nimble-components/src/mapping/empty/index.ts b/packages/nimble-components/src/mapping/empty/index.ts new file mode 100644 index 0000000000..81e763b26e --- /dev/null +++ b/packages/nimble-components/src/mapping/empty/index.ts @@ -0,0 +1,28 @@ +import { DesignSystem } from '@microsoft/fast-foundation'; +import { attr } from '@microsoft/fast-element'; +import { Mapping } from '../base'; +import { template } from '../base/template'; +import type { MappingKey } from '../base/types'; + +declare global { + interface HTMLElementTagNameMap { + 'nimble-mapping-empty': MappingEmpty; + } +} + +/** + * Maps data values to text. + * One or more may be added as children of a nimble-table-column-icon element. The + * mapping displays an empty cell and text-only group rows. + */ +export class MappingEmpty extends Mapping { + @attr() + public text?: string; +} + +const emptyMapping = MappingEmpty.compose({ + baseName: 'mapping-empty', + template +}); +DesignSystem.getOrCreate().withPrefix('nimble').register(emptyMapping()); +export const mappingEmptyTag = 'nimble-mapping-empty'; diff --git a/packages/nimble-components/src/mapping/empty/tests/mapping-empty.react.tsx b/packages/nimble-components/src/mapping/empty/tests/mapping-empty.react.tsx new file mode 100644 index 0000000000..a3fd4734e4 --- /dev/null +++ b/packages/nimble-components/src/mapping/empty/tests/mapping-empty.react.tsx @@ -0,0 +1,4 @@ +import { MappingEmpty } from '..'; +import { wrap } from '../../../utilities/tests/react-wrapper'; + +export const NimbleMappingEmpty = wrap(MappingEmpty); \ No newline at end of file diff --git a/packages/nimble-components/src/mapping/empty/tests/mapping-empty.spec.ts b/packages/nimble-components/src/mapping/empty/tests/mapping-empty.spec.ts new file mode 100644 index 0000000000..10cc1cc728 --- /dev/null +++ b/packages/nimble-components/src/mapping/empty/tests/mapping-empty.spec.ts @@ -0,0 +1,13 @@ +import { MappingEmpty, mappingEmptyTag } from '..'; + +describe('Empty Mapping', () => { + it('should export its tag', () => { + expect(mappingEmptyTag).toBe('nimble-mapping-empty'); + }); + + it('can construct an element instance', () => { + expect(document.createElement('nimble-mapping-empty')).toBeInstanceOf( + MappingEmpty + ); + }); +}); diff --git a/packages/nimble-components/src/mapping/empty/tests/mapping-empty.stories.ts b/packages/nimble-components/src/mapping/empty/tests/mapping-empty.stories.ts new file mode 100644 index 0000000000..f06cf910e2 --- /dev/null +++ b/packages/nimble-components/src/mapping/empty/tests/mapping-empty.stories.ts @@ -0,0 +1,36 @@ +import { html } from '@microsoft/fast-element'; +import type { Meta, StoryObj } from '@storybook/html'; +import { createUserSelectedThemeStory } from '../../../utilities/tests/storybook'; +import { hiddenWrapper } from '../../../utilities/tests/hidden'; +import { mappingKeyDescription } from '../../base/tests/story-helpers'; + +const metadata: Meta = { + title: 'Internal/Mappings', + parameters: { + docs: { + description: { + component: + `The \`nimble-mapping-empty\` element defines a mapping from a data value to text. It is meant to be used as content of the \`nimble-table-column-icon\` element + when the table cell should not include the mapped value. This mapping allows clients to avoid cluttering their table with information that isn't particularly + helpful to a user (e.g. that the state of a system is 'idle') while still having a good grouping experience that ensures group rows are not empty.` + } + } + } +}; + +export default metadata; + +export const emptyMapping: StoryObj = { + render: createUserSelectedThemeStory(hiddenWrapper(html``)), + argTypes: { + key: { + description: mappingKeyDescription('nothing'), + control: { type: 'none' } + }, + text: { + control: { type: 'none' }, + description: 'A textual description of the value. The table cells will render as empty, and the text will be displayed in a group header. This attribute is required.' + } + }, + args: {} +}; diff --git a/packages/nimble-components/src/table-column/enum-base/models/mapping-empty-config.ts b/packages/nimble-components/src/table-column/enum-base/models/mapping-empty-config.ts new file mode 100644 index 0000000000..de22fcd1a1 --- /dev/null +++ b/packages/nimble-components/src/table-column/enum-base/models/mapping-empty-config.ts @@ -0,0 +1,6 @@ +import { MappingConfig } from './mapping-config'; + +/** + * Mapping configuration corresponding to an empty mapping + */ +export class MappingEmptyConfig extends MappingConfig {} diff --git a/packages/nimble-components/src/table-column/icon/cell-view/index.ts b/packages/nimble-components/src/table-column/icon/cell-view/index.ts index e448f4d399..d72fde6a83 100644 --- a/packages/nimble-components/src/table-column/icon/cell-view/index.ts +++ b/packages/nimble-components/src/table-column/icon/cell-view/index.ts @@ -17,6 +17,7 @@ import { SpinnerView } from '../../enum-base/models/mapping-spinner-config'; import { MappingTextConfig } from '../../enum-base/models/mapping-text-config'; +import { MappingEmptyConfig } from '../../enum-base/models/mapping-empty-config'; declare global { interface HTMLElementTagNameMap { @@ -82,6 +83,9 @@ export class TableColumnIconCellView } else if (mappingConfig instanceof MappingTextConfig) { this.text = mappingConfig.text; this.textHidden = false; + } else if (mappingConfig instanceof MappingEmptyConfig) { + this.text = mappingConfig.text; + this.textHidden = true; } } diff --git a/packages/nimble-components/src/table-column/icon/group-header-view/index.ts b/packages/nimble-components/src/table-column/icon/group-header-view/index.ts index 87f142f036..ff2efefebe 100644 --- a/packages/nimble-components/src/table-column/icon/group-header-view/index.ts +++ b/packages/nimble-components/src/table-column/icon/group-header-view/index.ts @@ -15,6 +15,7 @@ import { SpinnerView } from '../../enum-base/models/mapping-spinner-config'; import { MappingTextConfig } from '../../enum-base/models/mapping-text-config'; +import { MappingEmptyConfig } from '../../enum-base/models/mapping-empty-config'; declare global { interface HTMLElementTagNameMap { @@ -58,6 +59,8 @@ export class TableColumnIconGroupHeaderView this.visualizationTemplate = mappingConfig.spinnerTemplate; } else if (mappingConfig instanceof MappingTextConfig) { this.text = mappingConfig.text ?? ''; + } else if (mappingConfig instanceof MappingEmptyConfig) { + this.text = mappingConfig.text ?? ''; } } diff --git a/packages/nimble-components/src/table-column/icon/index.ts b/packages/nimble-components/src/table-column/icon/index.ts index aab6876e28..0142907272 100644 --- a/packages/nimble-components/src/table-column/icon/index.ts +++ b/packages/nimble-components/src/table-column/icon/index.ts @@ -21,6 +21,8 @@ import { MappingIconConfig } from '../enum-base/models/mapping-icon-config'; import { MappingSpinnerConfig } from '../enum-base/models/mapping-spinner-config'; import { MappingText } from '../../mapping/text'; import { MappingTextConfig } from '../enum-base/models/mapping-text-config'; +import { MappingEmpty } from '../../mapping/empty'; +import { MappingEmptyConfig } from '../enum-base/models/mapping-empty-config'; declare global { interface HTMLElementTagNameMap { @@ -73,6 +75,9 @@ export class TableColumnIcon extends mixinGroupableColumnAPI( if (mapping instanceof MappingText) { return new MappingTextConfig(mapping.text); } + if (mapping instanceof MappingEmpty) { + return new MappingEmptyConfig(mapping.text); + } // Getting here would indicate a programming error, b/c validation will prevent // this function from running when there is an unsupported mapping. throw new Error('Unsupported mapping'); diff --git a/packages/nimble-components/src/table-column/icon/models/table-column-icon-validator.ts b/packages/nimble-components/src/table-column/icon/models/table-column-icon-validator.ts index 0fb5ec08ed..9441de4992 100644 --- a/packages/nimble-components/src/table-column/icon/models/table-column-icon-validator.ts +++ b/packages/nimble-components/src/table-column/icon/models/table-column-icon-validator.ts @@ -1,4 +1,5 @@ import type { Mapping } from '../../../mapping/base'; +import { MappingEmpty } from '../../../mapping/empty'; import { MappingIcon } from '../../../mapping/icon'; import { MappingSpinner } from '../../../mapping/spinner'; import { MappingText } from '../../../mapping/text'; @@ -33,11 +34,12 @@ export class TableColumnIconValidator extends TableColumnEnumBaseValidator< private static isSupportedMappingElement( mapping: Mapping - ): mapping is MappingIcon | MappingSpinner | MappingText { + ): mapping is MappingIcon | MappingSpinner | MappingText | MappingEmpty { return ( mapping instanceof MappingIcon || mapping instanceof MappingSpinner || mapping instanceof MappingText + || mapping instanceof MappingEmpty ); } diff --git a/packages/nimble-components/src/table-column/icon/tests/table-column-icon-matrix.stories.ts b/packages/nimble-components/src/table-column/icon/tests/table-column-icon-matrix.stories.ts index 83386cf248..618b93e8bf 100644 --- a/packages/nimble-components/src/table-column/icon/tests/table-column-icon-matrix.stories.ts +++ b/packages/nimble-components/src/table-column/icon/tests/table-column-icon-matrix.stories.ts @@ -12,6 +12,7 @@ import { mappingSpinnerTag } from '../../../mapping/spinner'; import { isChromatic } from '../../../utilities/tests/isChromatic'; import { iconXmarkTag } from '../../../icons/xmark'; import { mappingTextTag } from '../../../mapping/text'; +import { mappingEmptyTag } from '../../../mapping/empty'; const data = [ { @@ -41,6 +42,10 @@ const data = [ { id: '6', code: 6 + }, + { + id: '7', + code: 7 } ] as const; @@ -55,7 +60,7 @@ export default metadata; // prettier-ignore const component = (): ViewTemplate => html` - <${tableTag} id-field-name="id" style="height: 520px; ${isChromatic() ? '--ni-private-spinner-animation-play-state:paused' : ''}"> + <${tableTag} id-field-name="id" style="height: 600px; ${isChromatic() ? '--ni-private-spinner-animation-play-state:paused' : ''}"> <${tableColumnIconTag} field-name="code" key-type="number" @@ -68,7 +73,8 @@ const component = (): ViewTemplate => html` <${mappingSpinnerTag} key="3" text="Spinner, text-hidden" text-hidden> <${mappingIconTag} key="4" text="Undefined icon, text-hidden" text-hidden> <${mappingIconTag} key="5" text="Undefined icon"> - <${mappingTextTag} key="6" text="Text" + <${mappingTextTag} key="6" text="Text"> + <${mappingEmptyTag} key="7" text="Empty mapping"> <${tableColumnIconTag} field-name="code" diff --git a/packages/nimble-components/src/table-column/icon/tests/table-column-icon.mdx b/packages/nimble-components/src/table-column/icon/tests/table-column-icon.mdx index 2ddaecd5cd..7676e73e7e 100644 --- a/packages/nimble-components/src/table-column/icon/tests/table-column-icon.mdx +++ b/packages/nimble-components/src/table-column/icon/tests/table-column-icon.mdx @@ -1,6 +1,7 @@ import { Canvas, Meta, Controls, Title, Description } from '@storybook/blocks'; import { columnOperationBehavior } from '../../base/tests/table-column-stories-utils'; import * as tableColumnIconStories from './table-column-icon.stories'; +import * as emptyMappingStories from '../../../mapping/empty/tests/mapping-empty.stories'; import * as iconMappingStories from '../../../mapping/icon/tests/mapping-icon.stories'; import * as spinnerMappingStories from '../../../mapping/spinner/tests/mapping-spinner.stories'; import * as textMappingStories from '../../../mapping/text/tests/mapping-text.stories'; @@ -45,3 +46,8 @@ When setting a child mapping element's `Key` value to a string, you must wrap it + +## Empty Mapping + + + diff --git a/packages/nimble-components/src/table-column/icon/tests/table-column-icon.spec.ts b/packages/nimble-components/src/table-column/icon/tests/table-column-icon.spec.ts index 299243be3e..8e9dfbdcfa 100644 --- a/packages/nimble-components/src/table-column/icon/tests/table-column-icon.spec.ts +++ b/packages/nimble-components/src/table-column/icon/tests/table-column-icon.spec.ts @@ -19,28 +19,25 @@ import { spinnerTag } from '../../../spinner'; import { themeProviderTag } from '../../../theme-provider'; import { TableColumnIconPageObject } from '../testing/table-column-icon.pageobject'; import { mappingUserTag } from '../../../mapping/user'; +import { mappingEmptyTag } from '../../../mapping/empty'; interface SimpleTableRecord extends TableRecord { field1?: MappingKey | null; field2?: MappingKey | null; } -interface BasicIconMapping { +interface BaseMapping { key?: MappingKey; text?: string; - icon?: string; - textHidden?: boolean; } -interface BasicSpinnerMapping { - key?: MappingKey; - text?: string; +interface IconMapping extends BaseMapping { + icon?: string; textHidden?: boolean; } -interface BasicTextMapping { - key?: MappingKey; - text?: string; +interface SpinnerMapping extends BaseMapping { + textHidden?: boolean; } class Model { @@ -61,9 +58,10 @@ describe('TableColumnIcon', () => { // prettier-ignore async function setup(options: { keyType: MappingKeyType, - iconMappings?: BasicIconMapping[], - spinnerMappings?: BasicSpinnerMapping[], - textMappings?: BasicTextMapping[] + iconMappings?: IconMapping[], + spinnerMappings?: SpinnerMapping[], + textMappings?: BaseMapping[], + emptyMappings?: BaseMapping[] }): Promise>> { const source = new Model(); const result = await fixture>(html` @@ -71,7 +69,7 @@ describe('TableColumnIcon', () => { <${tableTag} ${ref('table')} style="width: 700px"> <${tableColumnIconTag} ${ref('col1')} field-name="field1" key-type="${options.keyType}"> Column 1 - ${repeat(() => options.iconMappings ?? [], html` + ${repeat(() => options.iconMappings ?? [], html` <${mappingIconTag} key="${x => x.key}" text="${x => x.text}" @@ -79,19 +77,25 @@ describe('TableColumnIcon', () => { ?text-hidden="${x => x.textHidden}"> `)} - ${repeat(() => options.spinnerMappings ?? [], html` + ${repeat(() => options.spinnerMappings ?? [], html` <${mappingSpinnerTag} key="${x => x.key}" text="${x => x.text}" ?text-hidden="${x => x.textHidden}"> `)} - ${repeat(() => options.textMappings ?? [], html` + ${repeat(() => options.textMappings ?? [], html` <${mappingTextTag} key="${x => x.key}" text="${x => x.text}" `)} + ${repeat(() => options.emptyMappings ?? [], html` + <${mappingEmptyTag} + key="${x => x.key}" + text="${x => x.text}" + + `)} <${themeProviderTag}>`, { source }); @@ -532,7 +536,7 @@ describe('TableColumnIcon', () => { expect(model.col1.validity.invalidIconName).toBeTrue(); }); - it('is invalid with missing spinner text value', async () => { + it('is invalid with missing text in spinner mapping', async () => { ({ connect, disconnect, model } = await setup({ keyType: MappingKeyType.string, spinnerMappings: [{ key: 'a' }] @@ -543,7 +547,7 @@ describe('TableColumnIcon', () => { expect(model.col1.validity.missingTextValue).toBeTrue(); }); - it('is invalid with missing spinner key value', async () => { + it('is invalid with missing key in spinner mapping', async () => { ({ connect, disconnect, model } = await setup({ keyType: MappingKeyType.string, spinnerMappings: [{ text: 'alpha' }] @@ -554,7 +558,7 @@ describe('TableColumnIcon', () => { expect(model.col1.validity.missingKeyValue).toBeTrue(); }); - it('is invalid with missing spinner text value', async () => { + it('is invalid with missing text in text mapping', async () => { ({ connect, disconnect, model } = await setup({ keyType: MappingKeyType.string, textMappings: [{ key: 'a' }] @@ -565,7 +569,7 @@ describe('TableColumnIcon', () => { expect(model.col1.validity.missingTextValue).toBeTrue(); }); - it('is invalid with missing text key value', async () => { + it('is invalid with missing key in text mapping', async () => { ({ connect, disconnect, model } = await setup({ keyType: MappingKeyType.string, textMappings: [{ text: 'alpha' }] @@ -575,6 +579,28 @@ describe('TableColumnIcon', () => { expect(model.col1.checkValidity()).toBeFalse(); expect(model.col1.validity.missingKeyValue).toBeTrue(); }); + + it('is invalid with missing text in empty mapping', async () => { + ({ connect, disconnect, model } = await setup({ + keyType: MappingKeyType.string, + emptyMappings: [{ key: 'a' }] + })); + await connect(); + await waitForUpdatesAsync(); + expect(model.col1.checkValidity()).toBeFalse(); + expect(model.col1.validity.missingTextValue).toBeTrue(); + }); + + it('is invalid with missing key in empty mapping', async () => { + ({ connect, disconnect, model } = await setup({ + keyType: MappingKeyType.string, + emptyMappings: [{ text: 'alpha' }] + })); + await connect(); + await waitForUpdatesAsync(); + expect(model.col1.checkValidity()).toBeFalse(); + expect(model.col1.validity.missingKeyValue).toBeTrue(); + }); }); describe('placeholder', () => { @@ -742,12 +768,17 @@ describe('TableColumnIcon', () => { { name: 'text mapping', type: 'text' + }, + { + name: 'empty mapping', + type: 'empty' } ] as const; - for (const mappingType of mappingTypes) { + const mappingsWithTextInCell = mappingTypes.filter(x => x.type !== 'empty'); + for (const mappingType of mappingsWithTextInCell) { // eslint-disable-next-line @typescript-eslint/no-loop-func - describe(`in ${mappingType.name}`, () => { + describe(`in cell with ${mappingType.name}`, () => { const longText = 'a very long value that should get ellipsized due to not fitting within the default cell width'; const shortText = 'short value'; const longTextRowIndex = 0; @@ -775,6 +806,10 @@ describe('TableColumnIcon', () => { textMappings: [ { key: 'text-long', text: longText }, { key: 'text-short', text: shortText } + ], + emptyMappings: [ + { key: 'empty-long', text: longText }, + { key: 'empty-short', text: shortText } ] })); pageObject = new TablePageObject( @@ -793,7 +828,7 @@ describe('TableColumnIcon', () => { await waitForUpdatesAsync(); }); - it('sets title when cell text is ellipsized', async () => { + it('sets title when text is ellipsized', async () => { columnPageObject.dispatchEventToCellText( longTextRowIndex, 0, @@ -808,7 +843,7 @@ describe('TableColumnIcon', () => { ).toBe(longText); }); - it('does not set title when cell text is fully visible', async () => { + it('does not set title when text is fully visible', async () => { columnPageObject.dispatchEventToCellText( shortTextRowIndex, 0, @@ -843,8 +878,62 @@ describe('TableColumnIcon', () => { ) ).toBe(''); }); + }); + } + + for (const mappingType of mappingTypes) { + // eslint-disable-next-line @typescript-eslint/no-loop-func + describe(`in group row with ${mappingType.name}`, () => { + const longText = 'a very long value that should get ellipsized due to not fitting within the default cell width'; + const shortText = 'short value'; + const longTextRowIndex = 0; + const shortTextRowIndex = 1; + + beforeEach(async () => { + ({ connect, disconnect, model } = await setup({ + keyType: MappingKeyType.string, + iconMappings: [ + { + key: 'icon-long', + text: longText, + icon: iconXmarkTag + }, + { + key: 'icon-short', + text: shortText, + icon: iconXmarkTag + } + ], + spinnerMappings: [ + { key: 'spinner-long', text: longText }, + { key: 'spinner-short', text: shortText } + ], + textMappings: [ + { key: 'text-long', text: longText }, + { key: 'text-short', text: shortText } + ], + emptyMappings: [ + { key: 'empty-long', text: longText }, + { key: 'empty-short', text: shortText } + ] + })); + pageObject = new TablePageObject( + model.table + ); + columnPageObject = new TableColumnIconPageObject( + pageObject + ); + await model.table.setData([ + { field1: `${mappingType.type}-long` }, + { field1: `${mappingType.type}-short` } + ]); + await connect(); + model.table.style.width = '200px'; + model.col1.groupIndex = 0; + await waitForUpdatesAsync(); + }); - it('sets title when group header text is ellipsized', async () => { + it('sets title when text is ellipsized', async () => { columnPageObject.dispatchEventToGroupHeaderText( longTextRowIndex, new MouseEvent('mouseover') @@ -857,7 +946,7 @@ describe('TableColumnIcon', () => { ).toBe(longText); }); - it('does not set title when group header text is fully visible', async () => { + it('does not set title when text is fully visible', async () => { columnPageObject.dispatchEventToGroupHeaderText( shortTextRowIndex, new MouseEvent('mouseover') From 3422bfe4ef06fecda89cdce1fbde3359a2fafb5e Mon Sep 17 00:00:00 2001 From: mollykreis <20542556+mollykreis@users.noreply.github.com> Date: Thu, 25 Apr 2024 16:16:57 -0500 Subject: [PATCH 02/13] Change files --- ...le-components-cde0ca9f-1259-4049-ae46-ead5829d5151.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/@ni-nimble-components-cde0ca9f-1259-4049-ae46-ead5829d5151.json diff --git a/change/@ni-nimble-components-cde0ca9f-1259-4049-ae46-ead5829d5151.json b/change/@ni-nimble-components-cde0ca9f-1259-4049-ae46-ead5829d5151.json new file mode 100644 index 0000000000..66e8cbe2a0 --- /dev/null +++ b/change/@ni-nimble-components-cde0ca9f-1259-4049-ae46-ead5829d5151.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "Create empty mapping element and support it in the icon column", + "packageName": "@ni/nimble-components", + "email": "20542556+mollykreis@users.noreply.github.com", + "dependentChangeType": "patch" +} From a398fff2b745c14b8ae13390c8abf18cf0291d5b Mon Sep 17 00:00:00 2001 From: mollykreis <20542556+mollykreis@users.noreply.github.com> Date: Mon, 6 May 2024 15:53:24 -0500 Subject: [PATCH 03/13] move stories --- .../src/mapping/empty/tests/mapping-empty.react.tsx | 4 ---- .../src/nimble/mapping/empty/mapping-empty.react.tsx | 4 ++++ .../src/nimble/mapping/empty}/mapping-empty.stories.ts | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) delete mode 100644 packages/nimble-components/src/mapping/empty/tests/mapping-empty.react.tsx create mode 100644 packages/storybook/src/nimble/mapping/empty/mapping-empty.react.tsx rename packages/{nimble-components/src/mapping/empty/tests => storybook/src/nimble/mapping/empty}/mapping-empty.stories.ts (90%) diff --git a/packages/nimble-components/src/mapping/empty/tests/mapping-empty.react.tsx b/packages/nimble-components/src/mapping/empty/tests/mapping-empty.react.tsx deleted file mode 100644 index a3fd4734e4..0000000000 --- a/packages/nimble-components/src/mapping/empty/tests/mapping-empty.react.tsx +++ /dev/null @@ -1,4 +0,0 @@ -import { MappingEmpty } from '..'; -import { wrap } from '../../../utilities/tests/react-wrapper'; - -export const NimbleMappingEmpty = wrap(MappingEmpty); \ No newline at end of file diff --git a/packages/storybook/src/nimble/mapping/empty/mapping-empty.react.tsx b/packages/storybook/src/nimble/mapping/empty/mapping-empty.react.tsx new file mode 100644 index 0000000000..e4feb92880 --- /dev/null +++ b/packages/storybook/src/nimble/mapping/empty/mapping-empty.react.tsx @@ -0,0 +1,4 @@ +import { MappingEmpty } from '@ni/nimble-components/dist/esm/mapping/empty'; +import { wrap } from '../../../utilities/react-wrapper'; + +export const NimbleMappingEmpty = wrap(MappingEmpty); \ No newline at end of file diff --git a/packages/nimble-components/src/mapping/empty/tests/mapping-empty.stories.ts b/packages/storybook/src/nimble/mapping/empty/mapping-empty.stories.ts similarity index 90% rename from packages/nimble-components/src/mapping/empty/tests/mapping-empty.stories.ts rename to packages/storybook/src/nimble/mapping/empty/mapping-empty.stories.ts index f06cf910e2..7309434663 100644 --- a/packages/nimble-components/src/mapping/empty/tests/mapping-empty.stories.ts +++ b/packages/storybook/src/nimble/mapping/empty/mapping-empty.stories.ts @@ -1,8 +1,8 @@ import { html } from '@microsoft/fast-element'; import type { Meta, StoryObj } from '@storybook/html'; -import { createUserSelectedThemeStory } from '../../../utilities/tests/storybook'; -import { hiddenWrapper } from '../../../utilities/tests/hidden'; -import { mappingKeyDescription } from '../../base/tests/story-helpers'; +import { mappingKeyDescription } from '../base/story-helpers'; +import { createUserSelectedThemeStory } from '../../../utilities/storybook'; +import { hiddenWrapper } from '../../../utilities/hidden'; const metadata: Meta = { title: 'Internal/Mappings', From 8df1d3fbcaca8e6d9588fe77a128dd7320c685c6 Mon Sep 17 00:00:00 2001 From: mollykreis <20542556+mollykreis@users.noreply.github.com> Date: Tue, 7 May 2024 10:33:01 -0500 Subject: [PATCH 04/13] fixes --- .../src/nimble/mapping/empty/mapping-empty.stories.ts | 6 +++--- .../mapping/table-column-mapping-matrix.stories.ts | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/storybook/src/nimble/mapping/empty/mapping-empty.stories.ts b/packages/storybook/src/nimble/mapping/empty/mapping-empty.stories.ts index 7309434663..5797a57c88 100644 --- a/packages/storybook/src/nimble/mapping/empty/mapping-empty.stories.ts +++ b/packages/storybook/src/nimble/mapping/empty/mapping-empty.stories.ts @@ -24,11 +24,11 @@ export const emptyMapping: StoryObj = { render: createUserSelectedThemeStory(hiddenWrapper(html``)), argTypes: { key: { - description: mappingKeyDescription('nothing'), - control: { type: 'none' } + control: false, + description: mappingKeyDescription('nothing') }, text: { - control: { type: 'none' }, + control: false, description: 'A textual description of the value. The table cells will render as empty, and the text will be displayed in a group header. This attribute is required.' } }, diff --git a/packages/storybook/src/nimble/table-column/mapping/table-column-mapping-matrix.stories.ts b/packages/storybook/src/nimble/table-column/mapping/table-column-mapping-matrix.stories.ts index 6f04c8f124..221eb147e8 100644 --- a/packages/storybook/src/nimble/table-column/mapping/table-column-mapping-matrix.stories.ts +++ b/packages/storybook/src/nimble/table-column/mapping/table-column-mapping-matrix.stories.ts @@ -9,6 +9,7 @@ import { mappingSpinnerTag } from '@ni/nimble-components/dist/esm/mapping/spinne import { mappingTextTag } from '@ni/nimble-components/dist/esm/mapping/text'; import { tableColumnMappingTag } from '@ni/nimble-components/dist/esm/table-column/mapping'; import { TableColumnMappingWidthMode } from '@ni/nimble-components/dist/esm/table-column/mapping/types'; +import { mappingEmptyTag } from '@ni/nimble-components/dist/esm/mapping/empty'; import { isChromatic } from '../../../utilities/isChromatic'; import { createMatrixThemeStory, From 5c18789af432b71d50eb11601fd42b4fe41a7e98 Mon Sep 17 00:00:00 2001 From: mollykreis <20542556+mollykreis@users.noreply.github.com> Date: Tue, 7 May 2024 10:41:50 -0500 Subject: [PATCH 05/13] Update table-column-mapping.spec.ts --- .../mapping/tests/table-column-mapping.spec.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/nimble-components/src/table-column/mapping/tests/table-column-mapping.spec.ts b/packages/nimble-components/src/table-column/mapping/tests/table-column-mapping.spec.ts index 490ec14fd4..de55ca7f6e 100644 --- a/packages/nimble-components/src/table-column/mapping/tests/table-column-mapping.spec.ts +++ b/packages/nimble-components/src/table-column/mapping/tests/table-column-mapping.spec.ts @@ -92,6 +92,12 @@ describe('TableColumnMapping', () => { text="${x => x.text}" `)} + ${repeat(() => options.textMappings ?? [], html` + <${mappingEmptyTag} + key="${x => x.key}" + text="${x => x.text}" + + `)} <${themeProviderTag}>`, { source }); @@ -770,7 +776,9 @@ describe('TableColumnMapping', () => { } ] as const; - const mappingsWithTextInCell = mappingTypes.filter(x => x.type !== 'empty'); + const mappingsWithTextInCell = mappingTypes.filter( + x => x.type !== 'empty' + ); parameterizeSuite(mappingsWithTextInCell, (suite, name, value) => { suite(`in cell with ${name}`, () => { const longText = 'a very long value that should get ellipsized due to not fitting within the default cell width'; From 53edb11bfb3fe24c06b4b1b1302e5b8752349e68 Mon Sep 17 00:00:00 2001 From: mollykreis <20542556+mollykreis@users.noreply.github.com> Date: Tue, 7 May 2024 11:08:43 -0500 Subject: [PATCH 06/13] fix test setup --- .../src/table-column/mapping/tests/table-column-mapping.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nimble-components/src/table-column/mapping/tests/table-column-mapping.spec.ts b/packages/nimble-components/src/table-column/mapping/tests/table-column-mapping.spec.ts index de55ca7f6e..792fd22ad5 100644 --- a/packages/nimble-components/src/table-column/mapping/tests/table-column-mapping.spec.ts +++ b/packages/nimble-components/src/table-column/mapping/tests/table-column-mapping.spec.ts @@ -92,7 +92,7 @@ describe('TableColumnMapping', () => { text="${x => x.text}" `)} - ${repeat(() => options.textMappings ?? [], html` + ${repeat(() => options.emptyMappings ?? [], html` <${mappingEmptyTag} key="${x => x.key}" text="${x => x.text}" From 7dd6ceb7841cfe0ab6ab34756a8b85f0cdcad38b Mon Sep 17 00:00:00 2001 From: mollykreis <20542556+mollykreis@users.noreply.github.com> Date: Tue, 7 May 2024 11:47:17 -0500 Subject: [PATCH 07/13] minor updates --- .../nimble-components/src/mapping/empty/index.ts | 2 +- .../mapping/tests/table-column-mapping.spec.ts | 12 ++++++------ .../src/nimble/mapping/empty/mapping-empty.react.tsx | 2 +- .../nimble/mapping/empty/mapping-empty.stories.ts | 5 ++--- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/packages/nimble-components/src/mapping/empty/index.ts b/packages/nimble-components/src/mapping/empty/index.ts index 81e763b26e..6ad0fb3225 100644 --- a/packages/nimble-components/src/mapping/empty/index.ts +++ b/packages/nimble-components/src/mapping/empty/index.ts @@ -12,7 +12,7 @@ declare global { /** * Maps data values to text. - * One or more may be added as children of a nimble-table-column-icon element. The + * One or more may be added as children of a nimble-table-column-mapping element. The * mapping displays an empty cell and text-only group rows. */ export class MappingEmpty extends Mapping { diff --git a/packages/nimble-components/src/table-column/mapping/tests/table-column-mapping.spec.ts b/packages/nimble-components/src/table-column/mapping/tests/table-column-mapping.spec.ts index 792fd22ad5..9a6cfc1bad 100644 --- a/packages/nimble-components/src/table-column/mapping/tests/table-column-mapping.spec.ts +++ b/packages/nimble-components/src/table-column/mapping/tests/table-column-mapping.spec.ts @@ -538,7 +538,7 @@ describe('TableColumnMapping', () => { expect(model.col1.validity.invalidIconName).toBeTrue(); }); - it('is invalid with missing text in spinner mapping', async () => { + it('is invalid with missing text in a spinner mapping', async () => { ({ connect, disconnect, model } = await setup({ keyType: MappingKeyType.string, spinnerMappings: [{ key: 'a' }] @@ -549,7 +549,7 @@ describe('TableColumnMapping', () => { expect(model.col1.validity.missingTextValue).toBeTrue(); }); - it('is invalid with missing key in spinner mapping', async () => { + it('is invalid with missing key in a spinner mapping', async () => { ({ connect, disconnect, model } = await setup({ keyType: MappingKeyType.string, spinnerMappings: [{ text: 'alpha' }] @@ -560,7 +560,7 @@ describe('TableColumnMapping', () => { expect(model.col1.validity.missingKeyValue).toBeTrue(); }); - it('is invalid with missing text in text mapping', async () => { + it('is invalid with missing text in a text mapping', async () => { ({ connect, disconnect, model } = await setup({ keyType: MappingKeyType.string, textMappings: [{ key: 'a' }] @@ -571,7 +571,7 @@ describe('TableColumnMapping', () => { expect(model.col1.validity.missingTextValue).toBeTrue(); }); - it('is invalid with missing key in text mapping', async () => { + it('is invalid with missing key in a text mapping', async () => { ({ connect, disconnect, model } = await setup({ keyType: MappingKeyType.string, textMappings: [{ text: 'alpha' }] @@ -582,7 +582,7 @@ describe('TableColumnMapping', () => { expect(model.col1.validity.missingKeyValue).toBeTrue(); }); - it('is invalid with missing text in empty mapping', async () => { + it('is invalid with missing text in an empty mapping', async () => { ({ connect, disconnect, model } = await setup({ keyType: MappingKeyType.string, emptyMappings: [{ key: 'a' }] @@ -593,7 +593,7 @@ describe('TableColumnMapping', () => { expect(model.col1.validity.missingTextValue).toBeTrue(); }); - it('is invalid with missing key in empty mapping', async () => { + it('is invalid with missing key in an empty mapping', async () => { ({ connect, disconnect, model } = await setup({ keyType: MappingKeyType.string, emptyMappings: [{ text: 'alpha' }] diff --git a/packages/storybook/src/nimble/mapping/empty/mapping-empty.react.tsx b/packages/storybook/src/nimble/mapping/empty/mapping-empty.react.tsx index e4feb92880..f2c9b1b23a 100644 --- a/packages/storybook/src/nimble/mapping/empty/mapping-empty.react.tsx +++ b/packages/storybook/src/nimble/mapping/empty/mapping-empty.react.tsx @@ -1,4 +1,4 @@ import { MappingEmpty } from '@ni/nimble-components/dist/esm/mapping/empty'; import { wrap } from '../../../utilities/react-wrapper'; -export const NimbleMappingEmpty = wrap(MappingEmpty); \ No newline at end of file +export const NimbleMappingEmpty = wrap(MappingEmpty); diff --git a/packages/storybook/src/nimble/mapping/empty/mapping-empty.stories.ts b/packages/storybook/src/nimble/mapping/empty/mapping-empty.stories.ts index 5797a57c88..154ede30e2 100644 --- a/packages/storybook/src/nimble/mapping/empty/mapping-empty.stories.ts +++ b/packages/storybook/src/nimble/mapping/empty/mapping-empty.stories.ts @@ -10,9 +10,8 @@ const metadata: Meta = { docs: { description: { component: - `The \`nimble-mapping-empty\` element defines a mapping from a data value to text. It is meant to be used as content of the \`nimble-table-column-icon\` element - when the table cell should not include the mapped value. This mapping allows clients to avoid cluttering their table with information that isn't particularly - helpful to a user (e.g. that the state of a system is 'idle') while still having a good grouping experience that ensures group rows are not empty.` + `The \`nimble-mapping-empty\` element defines a mapping from a data value to text. It is meant to be used as content of the \`nimble-table-column-mapping\` element + when the table cell should not include the mapped value and the column is groupable.` } } } From 4e268bbbfc2647ee749a9d8dc8dd1eba99cb06c6 Mon Sep 17 00:00:00 2001 From: mollykreis <20542556+mollykreis@users.noreply.github.com> Date: Wed, 8 May 2024 15:38:35 -0500 Subject: [PATCH 08/13] PR feedback --- .../mapping/group-header-view/index.ts | 20 +++++++++++-- .../tests/table-column-mapping.spec.ts | 30 +++++++++---------- .../specs/table-column-placeholder-hld.md | 5 +--- .../mapping/empty/mapping-empty.stories.ts | 7 +++-- .../mapping/table-column-mapping.mdx | 4 +-- 5 files changed, 40 insertions(+), 26 deletions(-) diff --git a/packages/nimble-components/src/table-column/mapping/group-header-view/index.ts b/packages/nimble-components/src/table-column/mapping/group-header-view/index.ts index e07c84e9c3..edc5d1532d 100644 --- a/packages/nimble-components/src/table-column/mapping/group-header-view/index.ts +++ b/packages/nimble-components/src/table-column/mapping/group-header-view/index.ts @@ -16,6 +16,7 @@ import { } from '../../enum-base/models/mapping-spinner-config'; import { MappingTextConfig } from '../../enum-base/models/mapping-text-config'; import { MappingEmptyConfig } from '../../enum-base/models/mapping-empty-config'; +import { TableGroupHeaderView } from '../../base/group-header-view'; declare global { interface HTMLElementTagNameMap { @@ -27,11 +28,18 @@ declare global { * The group header view for the mapping column */ export class TableColumnMappingGroupHeaderView - extends TableColumnTextGroupHeaderViewBase< + extends TableGroupHeaderView< TableFieldValue, TableColumnEnumColumnConfig > implements IconView, SpinnerView { + /** @internal */ + @observable + public hasOverflow = false; + + @observable + public text = ''; + @observable public severity: IconSeverity; @@ -42,7 +50,15 @@ export class TableColumnMappingGroupHeaderView public readonly textHidden = false; - protected updateText(): void { + private columnConfigChanged(): void { + this.updateState(); + } + + private groupHeaderValueChanged(): void { + this.updateState(); + } + + private updateState(): void { this.resetState(); if (!this.columnConfig) { diff --git a/packages/nimble-components/src/table-column/mapping/tests/table-column-mapping.spec.ts b/packages/nimble-components/src/table-column/mapping/tests/table-column-mapping.spec.ts index 9a6cfc1bad..9d3615ef5f 100644 --- a/packages/nimble-components/src/table-column/mapping/tests/table-column-mapping.spec.ts +++ b/packages/nimble-components/src/table-column/mapping/tests/table-column-mapping.spec.ts @@ -28,17 +28,17 @@ interface SimpleTableRecord extends TableRecord { field2?: MappingKey | null; } -interface BaseMapping { +interface TestBaseMapping { key?: MappingKey; text?: string; } -interface IconMapping extends BaseMapping { +interface TestIconMapping extends TestBaseMapping { icon?: string; textHidden?: boolean; } -interface SpinnerMapping extends BaseMapping { +interface TestSpinnerMapping extends TestBaseMapping { textHidden?: boolean; } @@ -60,10 +60,10 @@ describe('TableColumnMapping', () => { // prettier-ignore async function setup(options: { keyType: MappingKeyType, - iconMappings?: IconMapping[], - spinnerMappings?: SpinnerMapping[], - textMappings?: BaseMapping[], - emptyMappings?: BaseMapping[] + iconMappings?: TestIconMapping[], + spinnerMappings?: TestSpinnerMapping[], + textMappings?: TestBaseMapping[], + emptyMappings?: TestBaseMapping[] }): Promise>> { const source = new Model(); const result = await fixture>(html` @@ -71,7 +71,7 @@ describe('TableColumnMapping', () => { <${tableTag} ${ref('table')} style="width: 700px"> <${tableColumnMappingTag} ${ref('col1')} field-name="field1" key-type="${options.keyType}"> Column 1 - ${repeat(() => options.iconMappings ?? [], html` + ${repeat(() => options.iconMappings ?? [], html` <${mappingIconTag} key="${x => x.key}" text="${x => x.text}" @@ -79,20 +79,20 @@ describe('TableColumnMapping', () => { ?text-hidden="${x => x.textHidden}"> `)} - ${repeat(() => options.spinnerMappings ?? [], html` + ${repeat(() => options.spinnerMappings ?? [], html` <${mappingSpinnerTag} key="${x => x.key}" text="${x => x.text}" ?text-hidden="${x => x.textHidden}"> `)} - ${repeat(() => options.textMappings ?? [], html` + ${repeat(() => options.textMappings ?? [], html` <${mappingTextTag} key="${x => x.key}" text="${x => x.text}" `)} - ${repeat(() => options.emptyMappings ?? [], html` + ${repeat(() => options.emptyMappings ?? [], html` <${mappingEmptyTag} key="${x => x.key}" text="${x => x.text}" @@ -605,22 +605,22 @@ describe('TableColumnMapping', () => { }); }); - describe('placeholder', () => { + describe('placeholders are not used', () => { const testCases = [ { name: 'value is not specified', data: [{}], - groupValue: 'No value' + groupValue: '' }, { name: 'value is undefined', data: [{ field1: undefined }], - groupValue: 'No value' + groupValue: '' }, { name: 'value is null', data: [{ field1: null }], - groupValue: 'No value' + groupValue: '' }, { name: 'value is unmapped value', diff --git a/packages/nimble-components/src/table/specs/table-column-placeholder-hld.md b/packages/nimble-components/src/table/specs/table-column-placeholder-hld.md index 39f93b9dc8..6d36a44ada 100644 --- a/packages/nimble-components/src/table/specs/table-column-placeholder-hld.md +++ b/packages/nimble-components/src/table/specs/table-column-placeholder-hld.md @@ -119,17 +119,14 @@ The icon mapping column will not have a configuration for a placeholder. | Special-cased field values | Cell display | Group row display | | -------------------------- | ------------- | ------------------ | -| `undefined` | \ | `"No value"` | -| `null` | \ | `"No value"` | | Non-mapped value\* | \ | \ | \*This is considered invalid data from the table's perspective and should be fixed within the client application. Column best practices: -- Avoid mixing `undefined` and `null` as values for the same field. When grouping this will lead to two groups (one for `null` values and one for `undefined` values) that both have the text `"No value"`. - Avoid using values that do not correspond to a mapping for the column. -- To display an empty cell but have a non-blank group row, create a mapping of the record value to an `undefined` icon. +- To display an empty cell but have a non-blank group row, use a `nimble-mapping-empty` child element. #### Text mapping column diff --git a/packages/storybook/src/nimble/mapping/empty/mapping-empty.stories.ts b/packages/storybook/src/nimble/mapping/empty/mapping-empty.stories.ts index 154ede30e2..a818334157 100644 --- a/packages/storybook/src/nimble/mapping/empty/mapping-empty.stories.ts +++ b/packages/storybook/src/nimble/mapping/empty/mapping-empty.stories.ts @@ -10,8 +10,9 @@ const metadata: Meta = { docs: { description: { component: - `The \`nimble-mapping-empty\` element defines a mapping from a data value to text. It is meant to be used as content of the \`nimble-table-column-mapping\` element - when the table cell should not include the mapped value and the column is groupable.` + `The \`nimble-mapping-empty\` element can be provided as content of \`nimble-table-column-mapping\` to define data values that + should render as empty table cells while still having meaningful text on the value's group row. For each such data value, provide a + \`nimble-mapping-empty\` element that maps the value to the text to display on the group row for that value.` } } } @@ -28,7 +29,7 @@ export const emptyMapping: StoryObj = { }, text: { control: false, - description: 'A textual description of the value. The table cells will render as empty, and the text will be displayed in a group header. This attribute is required.' + description: 'A textual description of the value. This value will be displayed in group rows, but table cells will be empty. This attribute is required.' } }, args: {} diff --git a/packages/storybook/src/nimble/table-column/mapping/table-column-mapping.mdx b/packages/storybook/src/nimble/table-column/mapping/table-column-mapping.mdx index a8b0c19bc8..9e9097fa29 100644 --- a/packages/storybook/src/nimble/table-column/mapping/table-column-mapping.mdx +++ b/packages/storybook/src/nimble/table-column/mapping/table-column-mapping.mdx @@ -24,8 +24,8 @@ The renders specific number, boolean, or str ### Best Practices - Provide a mapping for every expected record value. Because grouping is performed on the record value, non-mapped record values can result in multiple groups without group labels. - - To improve grouping behavior of values that don't correspond to icons, explicitly map those values to an `undefined` icon. -- Avoid having multiple values that map to the same label because grouping and sorting is done on the record value rather than the mapped label. + - To improve grouping behavior of a value that shouldn't be rendered in table cells, use a `nimble-mapping-empty` element to provide text for that value's group row. +- Avoid having multiple values that map to the same text because grouping and sorting is done on the record value rather than the mapped text. ### Blazor Usage From d056902658889fae92add31f5f3a7ad8d8c4ab2b Mon Sep 17 00:00:00 2001 From: mollykreis <20542556+mollykreis@users.noreply.github.com> Date: Wed, 8 May 2024 15:58:16 -0500 Subject: [PATCH 09/13] format --- .../src/table-column/mapping/group-header-view/index.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/nimble-components/src/table-column/mapping/group-header-view/index.ts b/packages/nimble-components/src/table-column/mapping/group-header-view/index.ts index edc5d1532d..97854ef69d 100644 --- a/packages/nimble-components/src/table-column/mapping/group-header-view/index.ts +++ b/packages/nimble-components/src/table-column/mapping/group-header-view/index.ts @@ -4,7 +4,6 @@ import { styles } from './styles'; import { template } from './template'; import type { TableColumnEnumColumnConfig } from '../../enum-base'; import type { TableFieldValue } from '../../../table/types'; -import { TableColumnTextGroupHeaderViewBase } from '../../text-base/group-header-view'; import { IconSeverity } from '../../../icon-base/types'; import { MappingIconConfig, @@ -28,10 +27,7 @@ declare global { * The group header view for the mapping column */ export class TableColumnMappingGroupHeaderView - extends TableGroupHeaderView< - TableFieldValue, - TableColumnEnumColumnConfig - > + extends TableGroupHeaderView implements IconView, SpinnerView { /** @internal */ @observable From 5c0b34ed47545038d54a3601c7152313f614c830 Mon Sep 17 00:00:00 2001 From: mollykreis <20542556+mollykreis@users.noreply.github.com> Date: Thu, 9 May 2024 10:22:23 -0500 Subject: [PATCH 10/13] two more tests --- .../tests/table-column-mapping.spec.ts | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/packages/nimble-components/src/table-column/mapping/tests/table-column-mapping.spec.ts b/packages/nimble-components/src/table-column/mapping/tests/table-column-mapping.spec.ts index 9d3615ef5f..27ea09be7a 100644 --- a/packages/nimble-components/src/table-column/mapping/tests/table-column-mapping.spec.ts +++ b/packages/nimble-components/src/table-column/mapping/tests/table-column-mapping.spec.ts @@ -368,6 +368,38 @@ describe('TableColumnMapping', () => { expect(() => columnPageObject.getRenderedGroupHeaderIconTagName(0)).toThrowError(); }); + it('empty mapping displays no value in cell', async () => { + ({ connect, disconnect, model } = await setup({ + keyType: MappingKeyType.string, + emptyMappings: [{ key: 'a', text: 'alpha' }] + })); + pageObject = new TablePageObject(model.table); + columnPageObject = new TableColumnMappingPageObject(pageObject); + await model.table.setData([{ field1: 'a' }]); + await connect(); + await waitForUpdatesAsync(); + + expect(() => pageObject.getRenderedMappingColumnCellIconTagName(0, 0)).toThrowError(); + expect(pageObject.getRenderedCellTextContent(0, 0)).toBe(''); + }); + + it('empty mapping displays text in group row', async () => { + ({ connect, disconnect, model } = await setup({ + keyType: MappingKeyType.string, + emptyMappings: [{ key: 'a', text: 'alpha' }] + })); + pageObject = new TablePageObject(model.table); + columnPageObject = new TableColumnMappingPageObject(pageObject); + await model.table.setData([{ field1: 'a' }]); + await connect(); + await waitForUpdatesAsync(); + model.col1.groupIndex = 0; + await waitForUpdatesAsync(); + + expect(() => columnPageObject.getRenderedGroupHeaderIconTagName(0)).toThrowError(); + expect(pageObject.getRenderedGroupHeaderTextContent(0)).toBe('alpha'); + }); + describe('validation', () => { it('is valid with no mappings', async () => { ({ connect, disconnect, model } = await setup({ From d0e2a132cc58587ec33ebdc155a5fc509e4f823c Mon Sep 17 00:00:00 2001 From: mollykreis <20542556+mollykreis@users.noreply.github.com> Date: Thu, 9 May 2024 12:46:35 -0500 Subject: [PATCH 11/13] PR feedback --- .../src/nimble/table-column/mapping/table-column-mapping.mdx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/storybook/src/nimble/table-column/mapping/table-column-mapping.mdx b/packages/storybook/src/nimble/table-column/mapping/table-column-mapping.mdx index 9e9097fa29..7e75c363ca 100644 --- a/packages/storybook/src/nimble/table-column/mapping/table-column-mapping.mdx +++ b/packages/storybook/src/nimble/table-column/mapping/table-column-mapping.mdx @@ -6,6 +6,7 @@ import * as iconMappingStories from '../../mapping/icon/mapping-icon.stories'; import * as spinnerMappingStories from '../../mapping/spinner/mapping-spinner.stories'; import * as textMappingStories from '../../mapping/text/mapping-text.stories'; import { tableColumnMappingTag } from '@ni/nimble-components/dist/esm/table-column/mapping'; +import { mappingEmptyTag } from '@ni/nimble-components/dist/esm/mapping/empty'; import { tableTag } from '@ni/nimble-components/dist/esm/table'; import { spinnerTag } from '@ni/nimble-components/dist/esm/spinner'; @@ -24,7 +25,7 @@ The renders specific number, boolean, or str ### Best Practices - Provide a mapping for every expected record value. Because grouping is performed on the record value, non-mapped record values can result in multiple groups without group labels. - - To improve grouping behavior of a value that shouldn't be rendered in table cells, use a `nimble-mapping-empty` element to provide text for that value's group row. + - To improve grouping behavior of a value that shouldn't be rendered in table cells, use a element to provide text for that value's group row. - Avoid having multiple values that map to the same text because grouping and sorting is done on the record value rather than the mapped text. ### Blazor Usage From 11cb6c221b848200ae52785b2275afa728642177 Mon Sep 17 00:00:00 2001 From: mollykreis <20542556+mollykreis@users.noreply.github.com> Date: Fri, 10 May 2024 09:51:36 -0500 Subject: [PATCH 12/13] Replace undefined-icon icon mapping with empty mapping --- .../table-column/mapping/table-column-mapping.stories.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/storybook/src/nimble/table-column/mapping/table-column-mapping.stories.ts b/packages/storybook/src/nimble/table-column/mapping/table-column-mapping.stories.ts index b294bd5d50..24c6ecfb57 100644 --- a/packages/storybook/src/nimble/table-column/mapping/table-column-mapping.stories.ts +++ b/packages/storybook/src/nimble/table-column/mapping/table-column-mapping.stories.ts @@ -18,6 +18,7 @@ import { } from '../base/table-column-stories-utils'; import { isChromatic } from '../../../utilities/isChromatic'; import { createUserSelectedThemeStory } from '../../../utilities/storybook'; +import { mappingEmptyTag } from '@ni/nimble-components/dist/esm/mapping/empty'; const simpleData = [ { @@ -97,7 +98,7 @@ export const mappingColumn: StoryObj = { <${mappingIconTag} key="fail" icon="${iconXmarkTag}" severity="error" text="Not a Simpson"> <${mappingIconTag} key="success" icon="${iconCheckLargeTag}" severity="success" text="Is a Simpson"> <${mappingSpinnerTag} key="calculating" text="Calculating" text-hidden> - <${mappingIconTag} key="unknown" text="Unknown" text-hidden> + <${mappingEmptyTag} key="unknown" text="Unknown"> <${tableColumnMappingTag} field-name="isChild" key-type="boolean" width-mode="${x => TableColumnMappingWidthMode[x.widthMode]}"> <${iconChartDiagramChildFocusTag} title="Is child"> From bbe7ef609d4fb7a51e89f609c72cabcbe0fe3514 Mon Sep 17 00:00:00 2001 From: mollykreis <20542556+mollykreis@users.noreply.github.com> Date: Fri, 10 May 2024 10:26:19 -0500 Subject: [PATCH 13/13] Update table-column-mapping.stories.ts --- .../nimble/table-column/mapping/table-column-mapping.stories.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/storybook/src/nimble/table-column/mapping/table-column-mapping.stories.ts b/packages/storybook/src/nimble/table-column/mapping/table-column-mapping.stories.ts index 24c6ecfb57..d8f91595ee 100644 --- a/packages/storybook/src/nimble/table-column/mapping/table-column-mapping.stories.ts +++ b/packages/storybook/src/nimble/table-column/mapping/table-column-mapping.stories.ts @@ -9,6 +9,7 @@ import { mappingIconTag } from '@ni/nimble-components/dist/esm/mapping/icon'; import { mappingSpinnerTag } from '@ni/nimble-components/dist/esm/mapping/spinner'; import { sharedMappingValidityDescription } from '@ni/nimble-components/dist/esm/table-column/enum-base/tests/shared-storybook-docs'; import { mappingTextTag } from '@ni/nimble-components/dist/esm/mapping/text'; +import { mappingEmptyTag } from '@ni/nimble-components/dist/esm/mapping/empty'; import { TableColumnMappingWidthMode } from '@ni/nimble-components/dist/esm/table-column/mapping/types'; import { tableColumnMappingTag } from '@ni/nimble-components/dist/esm/table-column/mapping'; import { @@ -18,7 +19,6 @@ import { } from '../base/table-column-stories-utils'; import { isChromatic } from '../../../utilities/isChromatic'; import { createUserSelectedThemeStory } from '../../../utilities/storybook'; -import { mappingEmptyTag } from '@ni/nimble-components/dist/esm/mapping/empty'; const simpleData = [ {