Skip to content

Commit

Permalink
Updated parameterized test pattern (#1379)
Browse files Browse the repository at this point in the history
# Pull Request

## 🤨 Rationale

Updates the parametrized test pattern to address a few shortcomings in
the previous pattern:
- Avoids a lot of duplicated boilerplate
- Doesn't encourage an approach that results in eslint disables being
used often (false positive of await used in a loop)
- Prevents accidentally focusing a test and checking it in
- Gives decent typing, may take run-time to see if it needs improvements

## 👩‍💻 Implementation

Exposed new parameterization helpers named `parameterize` and
`parameterizeNamedList` and marked the existing method deprecated.
Created a tech debt task to track migrating to the new pattern:
#1551

## 🧪 Testing

Added some tests for the new helpers and migrated a couple of existing
tests to the new pattern.

## ✅ Checklist

- [x] I have updated the project documentation to reflect my changes or
determined no changes are needed. Added detailed comments to the helper
like the deprecated helper did.
  • Loading branch information
rajsite authored Sep 19, 2023
1 parent cc709bb commit eadf3f2
Show file tree
Hide file tree
Showing 5 changed files with 438 additions and 86 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "none",
"comment": "Parameterize test pattern",
"packageName": "@ni/nimble-components",
"email": "[email protected]",
"dependentChangeType": "none"
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { html } from '@microsoft/fast-element';
import { AnchorButton } from '..';
import { waitForUpdatesAsync } from '../../testing/async-helpers';
import { fixture, Fixture } from '../../utilities/tests/fixture';
import { getSpecTypeByNamedList } from '../../utilities/tests/parameterized';
import { parameterizeNamedList } from '../../utilities/tests/parameterized';

async function setup(): Promise<Fixture<AnchorButton>> {
return fixture<AnchorButton>(
Expand Down Expand Up @@ -49,7 +49,7 @@ describe('AnchorButton', () => {
expect(element.control!.getAttribute('href')).toBeNull();
});

const attributeNames: { name: string }[] = [
const attributeNames = [
{ name: 'download' },
{ name: 'hreflang' },
{ name: 'ping' },
Expand Down Expand Up @@ -77,27 +77,17 @@ describe('AnchorButton', () => {
{ name: 'aria-owns' },
{ name: 'aria-relevant' },
{ name: 'aria-roledescription' }
];
] as const;
describe('should reflect value to the internal control', () => {
const focused: string[] = [];
const disabled: string[] = [];
for (const attribute of attributeNames) {
const specType = getSpecTypeByNamedList(
attribute,
focused,
disabled
);
// eslint-disable-next-line @typescript-eslint/no-loop-func
specType(`for attribute ${attribute.name}`, async () => {
parameterizeNamedList(attributeNames, (spec, name) => {
spec(`for attribute ${name}`, async () => {
await connect();

element.setAttribute(attribute.name, 'foo');
element.setAttribute(name, 'foo');
await waitForUpdatesAsync();

expect(element.control!.getAttribute(attribute.name)).toBe(
'foo'
);
expect(element.control!.getAttribute(name)).toBe('foo');
});
}
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import '../../anchor-tab';
import type { AnchorTab } from '../../anchor-tab';
import { waitForUpdatesAsync } from '../../testing/async-helpers';
import { fixture, Fixture } from '../../utilities/tests/fixture';
import { getSpecTypeByNamedList } from '../../utilities/tests/parameterized';
import { parameterizeNamedList } from '../../utilities/tests/parameterized';

describe('AnchorTabs', () => {
let element: AnchorTabs;
Expand Down Expand Up @@ -182,168 +182,177 @@ describe('AnchorTabs', () => {
await disconnect();
});

const navigationTests: {
name: string,
disabledIndex?: number,
hiddenIndex?: number,
initialFocusIndex: number,
keyName: string,
expectedFinalFocusIndex: number
}[] = [
const navigationTests = [
{
name: 'should focus next tab when right arrow key pressed',
disabledIndex: undefined,
hiddenIndex: undefined,
initialFocusIndex: 0,
keyName: keyArrowRight,
expectedFinalFocusIndex: 1
},
{
name: 'should focus previous tab when left arrow key pressed',
disabledIndex: undefined,
hiddenIndex: undefined,
initialFocusIndex: 1,
keyName: keyArrowLeft,
expectedFinalFocusIndex: 0
},
{
name: 'should wrap to first tab when arrowing right on last tab',
disabledIndex: undefined,
hiddenIndex: undefined,
initialFocusIndex: 2,
keyName: keyArrowRight,
expectedFinalFocusIndex: 0
},
{
name: 'should wrap to last tab when arrowing left on first tab',
disabledIndex: undefined,
hiddenIndex: undefined,
initialFocusIndex: 0,
keyName: keyArrowLeft,
expectedFinalFocusIndex: 2
},
{
name: 'should focus first tab when Home key pressed',
disabledIndex: undefined,
hiddenIndex: undefined,
initialFocusIndex: 1,
keyName: keyHome,
expectedFinalFocusIndex: 0
},
{
name: 'should focus last tab when End key pressed',
disabledIndex: undefined,
hiddenIndex: undefined,
initialFocusIndex: 1,
keyName: keyEnd,
expectedFinalFocusIndex: 2
},
{
name: 'should skip disabled tab when arrowing right',
disabledIndex: 1,
hiddenIndex: undefined,
initialFocusIndex: 0,
keyName: keyArrowRight,
expectedFinalFocusIndex: 2
},
{
name: 'should skip disabled tab when arrowing left',
disabledIndex: 1,
hiddenIndex: undefined,
initialFocusIndex: 2,
keyName: keyArrowLeft,
expectedFinalFocusIndex: 0
},
{
name: 'should skip disabled when arrowing right on last tab',
disabledIndex: 0,
hiddenIndex: undefined,
initialFocusIndex: 2,
keyName: keyArrowRight,
expectedFinalFocusIndex: 1
},
{
name: 'should skip disabled when arrowing left on first tab',
disabledIndex: 2,
hiddenIndex: undefined,
initialFocusIndex: 0,
keyName: keyArrowLeft,
expectedFinalFocusIndex: 1
},
{
name: 'should focus first enabled tab when Home key pressed',
disabledIndex: 0,
hiddenIndex: undefined,
initialFocusIndex: 2,
keyName: keyHome,
expectedFinalFocusIndex: 1
},
{
name: 'should focus last enabled tab when End key pressed',
disabledIndex: 2,
hiddenIndex: undefined,
initialFocusIndex: 0,
keyName: keyEnd,
expectedFinalFocusIndex: 1
},
{
name: 'should skip hidden tab when arrowing right',
disabledIndex: undefined,
hiddenIndex: 1,
initialFocusIndex: 0,
keyName: keyArrowRight,
expectedFinalFocusIndex: 2
},
{
name: 'should skip hidden tab when arrowing left',
disabledIndex: undefined,
hiddenIndex: 1,
initialFocusIndex: 2,
keyName: keyArrowLeft,
expectedFinalFocusIndex: 0
},
{
name: 'should skip hidden when arrowing right on last tab',
disabledIndex: undefined,
hiddenIndex: 0,
initialFocusIndex: 2,
keyName: keyArrowRight,
expectedFinalFocusIndex: 1
},
{
name: 'should skip hidden when arrowing left on first tab',
disabledIndex: undefined,
hiddenIndex: 2,
initialFocusIndex: 0,
keyName: keyArrowLeft,
expectedFinalFocusIndex: 1
},
{
name: 'should focus first visible tab when Home key pressed',
disabledIndex: undefined,
hiddenIndex: 0,
initialFocusIndex: 2,
keyName: keyHome,
expectedFinalFocusIndex: 1
},
{
name: 'should focus last visible tab when End key pressed',
disabledIndex: undefined,
hiddenIndex: 2,
initialFocusIndex: 0,
keyName: keyEnd,
expectedFinalFocusIndex: 1
}
];
] as const;
describe('navigation', () => {
const focused: string[] = [];
const disabled: string[] = [];
for (const test of navigationTests) {
const specType = getSpecTypeByNamedList(
test,
focused,
disabled
);
// eslint-disable-next-line @typescript-eslint/no-loop-func
specType(test.name, async () => {
parameterizeNamedList(navigationTests, (spec, name, value) => {
spec(name, async () => {
await connect();
if (test.disabledIndex !== undefined) {
tab(test.disabledIndex).disabled = true;
if (value.disabledIndex !== undefined) {
tab(value.disabledIndex).disabled = true;
await waitForUpdatesAsync();
}
if (test.hiddenIndex !== undefined) {
tab(test.hiddenIndex).hidden = true;
if (value.hiddenIndex !== undefined) {
tab(value.hiddenIndex).hidden = true;
await waitForUpdatesAsync();
}
tab(test.initialFocusIndex).focus();
tab(test.initialFocusIndex).dispatchEvent(
new KeyboardEvent('keydown', { key: test.keyName })
tab(value.initialFocusIndex).focus();
tab(value.initialFocusIndex).dispatchEvent(
new KeyboardEvent('keydown', { key: value.keyName })
);
await waitForUpdatesAsync();
expect(document.activeElement).toBe(
tab(test.expectedFinalFocusIndex)
);
expect(tab(test.expectedFinalFocusIndex).ariaSelected).toBe(
'true'
tab(value.expectedFinalFocusIndex)
);
expect(
tab(value.expectedFinalFocusIndex).ariaSelected
).toBe('true');
});
}
});
});

it('should skip past other tabs when pressing tab key after click', async () => {
Expand Down
Loading

0 comments on commit eadf3f2

Please sign in to comment.