Skip to content

Commit

Permalink
feat: refactor anchor-button to use element internals and anchor prox…
Browse files Browse the repository at this point in the history
…y element (#31653)
  • Loading branch information
chrisdholt authored Jun 11, 2024
1 parent 197ceb2 commit 46c3369
Show file tree
Hide file tree
Showing 9 changed files with 232 additions and 589 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "refactor anchor-button to use ElementInternals, remove disabled and disabledFocusable",
"packageName": "@fluentui/web-components",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,4 @@ export const definition = AnchorButton.compose({
name: `${FluentDesignSystem.prefix}-anchor-button`,
template,
styles,
shadowOptions: {
delegatesFocus: true,
},
});
23 changes: 23 additions & 0 deletions packages/web-components/src/anchor-button/anchor-button.options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,26 @@ export const AnchorTarget = {
* @public
*/
export type AnchorTarget = ValuesOf<typeof AnchorTarget>;

/**
* Reflected anchor attributes.
*
* @public
*/
export const AnchorAttributes = {
download: 'download',
href: 'href',
hreflang: 'hreflang',
ping: 'ping',
referrerpolicy: 'referrerpolicy',
rel: 'rel',
target: 'target',
type: 'type',
} as const;

/**
* Type for anchor attributes.
*
* @public
*/
export type AnchorAttributes = ValuesOf<typeof AnchorAttributes>;
120 changes: 38 additions & 82 deletions packages/web-components/src/anchor-button/anchor-button.spec.ts
Original file line number Diff line number Diff line change
@@ -1,124 +1,80 @@
import { spinalCase } from '@microsoft/fast-web-utilities';
import { expect, test } from '@playwright/test';
import type { Locator, Page } from '@playwright/test';
import { fixtureURL } from '../helpers.tests.js';

// Regular Attributes
const attributes = {
appearance: 'primary',
shape: 'rounded',
size: 'medium',
const proxyAttributes = {
href: 'href',
ping: 'ping',
hreflang: 'en-GB',
referrerpolicy: 'no-referrer',
rel: 'external',
target: '_blank',
type: 'foo',
ariaControls: 'testId',
ariaCurrent: 'page',
ariaDescribedby: 'testId',
ariaDetails: 'testId',
ariaErrormessage: 'test',
ariaFlowto: 'testId',
ariaInvalid: 'spelling',
ariaKeyshortcuts: 'F4',
ariaLabel: 'foo',
ariaLabelledby: 'testId',
ariaLive: 'polite',
ariaOwns: 'testId',
ariaRelevant: 'removals',
ariaRoledescription: 'slide',
};

// Regular Attributes
const attributes = {
appearance: 'primary',
shape: 'rounded',
size: 'medium',
...proxyAttributes,
};

// Boolean Attributes
const booleanAttributes = {
iconOnly: true,
disabled: true,
disabledFocusable: true,
ariaAtomic: true,
ariaBusy: false,
ariaDisabled: true,
ariaExpanded: true,
ariaHaspopup: true,
ariaHidden: true,
};

test.describe('Anchor Button - Regular Attributes', () => {
let page: Page;
let element: Locator;

test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
await page.goto(fixtureURL('components-button-anchor--anchor-button', attributes));
element = page.locator('fluent-anchor-button');
});
test.describe('Anchor Button', () => {
test.beforeEach(async ({ page }) => {
await page.goto(fixtureURL('components-button-anchor--anchor-button'));

test.afterAll(async () => {
await page.close();
await page.waitForFunction(() => customElements.whenDefined('fluent-anchor-button'));
});

for (const [attribute, value] of Object.entries(attributes)) {
const attributeSpinalCase = spinalCase(attribute);

test(`should set the regular attribute: \`${attributeSpinalCase}\` to \`${value}\` on the internal control`, async () => {
await element.evaluate(
(node: any, { attribute, value }) => {
node.setAttribute(attribute, value);
},
{ attribute: attributeSpinalCase, value },
);
test(`should set the regular attribute: \`${attributeSpinalCase}\` to \`${value}\` on the element`, async ({
page,
}) => {
const element = page.locator('fluent-anchor-button');

await page.setContent(/* html */ `
<fluent-anchor-button ${attributeSpinalCase}="${value}"></fluent-anchor-button>
`);

await expect(element).toHaveJSProperty(`${attribute}`, `${value}`);
});
}
});

test.describe('Anchor Button - Boolean Attributes', () => {
let page: Page;
let element: Locator;

test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
await page.goto(fixtureURL('components-button-anchor--anchor-button', booleanAttributes));
element = page.locator('fluent-anchor-button');
});

test.afterAll(async () => {
await page.close();
});

// Boolean attributes
for (const [attribute, value] of Object.entries(booleanAttributes)) {
const attributeSpinalCase = spinalCase(attribute);

test(`should set the boolean attribute: \`${attributeSpinalCase}\` to \`${value}\``, async () => {
await element.evaluate(
(node: any, { attribute, value }) => {
node[attribute] = value;
},
{ attribute, value },
);
test(`should set the boolean attribute: \`${attributeSpinalCase}\` to \`${value}\``, async ({ page }) => {
const element = page.locator('fluent-anchor-button');

await page.setContent(/* html */ `
<fluent-anchor-button ${attributeSpinalCase}></fluent-anchor-button>
`);

await expect(element).toHaveJSProperty(attribute, value);
});
}

test(`should have transparent border when the \`disabled\` or \`disabled-focus\` attribute is present`, async ({
page,
}) => {
await element.evaluate((node: any) => {
node.setAttribute('appearance', 'primary');
node.setAttribute('disabled', true);
});
for (const [attribute, value] of Object.entries(proxyAttributes)) {
test(`should set the regular attribute: \`${attribute}\` to \`${value}\` on the internal proxy`, async ({
page,
}) => {
const element = page.locator('fluent-anchor-button');
const proxy = element.locator('a');

await expect(element).toHaveCSS('border-color', 'rgb(0, 0, 0)');
await page.setContent(/* html */ `
<fluent-anchor-button ${attribute}="${value}"></fluent-anchor-button>
`);

await element.evaluate((node: any) => {
node.setAttribute('disabled', false); // Reset
node.setAttribute('disabled-focusable', true);
await expect(proxy).toHaveAttribute(`${attribute}`, `${value}`);
});

await expect(element).toHaveCSS('border-color', 'rgb(0, 0, 0)');
});
}
});
33 changes: 0 additions & 33 deletions packages/web-components/src/anchor-button/anchor-button.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ const storyTemplate = html<AnchorButtonStoryArgs>`
appearance="${x => x.appearance}"
shape="${x => x.shape}"
size="${x => x.size}"
?disabled="${x => x.disabled}"
?disabled-focusable="${x => x.disabledFocusable}"
?icon-only="${x => x.iconOnly}"
>
${x => x.content}
Expand Down Expand Up @@ -49,28 +47,6 @@ export default {
type: 'select',
},
},
disabled: {
control: 'boolean',
table: {
type: {
summary: 'Sets the disabled state of the component',
},
defaultValue: {
summary: 'false',
},
},
},
disabledFocusable: {
control: 'boolean',
table: {
type: {
summary: 'The component is disabled but still focusable',
},
defaultValue: {
summary: 'false',
},
},
},
href: {
control: 'text',
},
Expand Down Expand Up @@ -198,15 +174,6 @@ export const Size = renderComponent(html<AnchorButtonStoryArgs>`
</fluent-anchor-button>
`);

export const Disabled = renderComponent(html<AnchorButtonStoryArgs>`
<fluent-anchor-button href="#">Enabled state</fluent-anchor-button>
<fluent-anchor-button href="#" disabled>Disabled state</fluent-anchor-button>
<fluent-anchor-button href="#" disabled-focusable>Disabled focusable state</fluent-anchor-button>
<fluent-anchor-button href="#" appearance="primary">Enabled state</fluent-anchor-button>
<fluent-anchor-button href="#" appearance="primary" disabled>Disabled state</fluent-anchor-button>
<fluent-anchor-button href="#" appearance="primary" disabled-focusable>Disabled focusable state</fluent-anchor-button>
`);

export const WithLongText = renderComponent(html<AnchorButtonStoryArgs>`
<style>
.max-width {
Expand Down
Loading

0 comments on commit 46c3369

Please sign in to comment.