Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"type": "patch",
"comment": "fix: defer fallback custom-state attributes until after construction",
"packageName": "@fluentui/web-components",
"email": "16242628+sylvesterkaczmarek@users.noreply.github.com"
}
69 changes: 69 additions & 0 deletions packages/web-components/src/tablist/tablist.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,75 @@ test.describe('Tablist', () => {
expect(hasError).toBe(false);
});

test.describe('custom state fallback', () => {
test.use({ ssr: false });

test.beforeEach(async ({ page, fastPage }) => {
await page.addInitScript(() => {
const supports = CSS.supports.bind(CSS);
CSS.supports = (property: string, value?: string): boolean => {
if (property === 'selector(:state(g))') {
return false;
}
return value === undefined ? supports(property) : supports(property, value);
};
});
await fastPage.goto();
});

test('should construct without adding host attributes', async ({ page }) => {
const errors: Error[] = [];
page.on('pageerror', error => errors.push(error));

const result = await page.evaluate(tagName => {
const element = document.createElement(tagName);
return {
upgraded: element instanceof customElements.get(tagName)!,
attributes: element.getAttributeNames(),
};
}, tagName);

expect(result).toEqual({ upgraded: true, attributes: [] });
expect(errors).toEqual([]);
});

test('should apply default states after construction', async ({ page }) => {
await page.evaluate(tagName => {
document.body.append(document.createElement(tagName));
}, tagName);

await expect(page.locator(tagName)).toHaveAttribute('state--horizontal');
await expect(page.locator(tagName)).not.toHaveAttribute('state--vertical');
await expect(page.locator(tagName)).not.toHaveAttribute('state--disabled');
});

test('should retain the latest states when updated before connection', async ({ page }) => {
await page.evaluate(tagName => {
Comment thread
sylvesterkaczmarek marked this conversation as resolved.
const element = document.createElement(tagName) as Tablist;
element.orientation = 'vertical';
element.orientation = 'horizontal';
element.orientation = 'vertical';
element.disabled = true;
element.disabled = false;
element.disabled = true;
document.body.append(element);
}, tagName);

const element = page.locator(tagName);
await expect(element).toHaveAttribute('state--vertical');
await expect(element).not.toHaveAttribute('state--horizontal');
await expect(element).toHaveAttribute('state--disabled');

await element.evaluate((node: Tablist) => {
node.disabled = false;
node.orientation = 'horizontal';
});
await expect(element).not.toHaveAttribute('state--disabled');
await expect(element).not.toHaveAttribute('state--vertical');
await expect(element).toHaveAttribute('state--horizontal');
});
});

test('should have reflect disabled attribute on control', async ({ fastPage }) => {
const { element } = fastPage;

Expand Down
4 changes: 3 additions & 1 deletion packages/web-components/src/utils/element-internals.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Updates } from '@microsoft/fast-element';
import { CustomStatesSetSupported } from './support.js';

/**
Expand Down Expand Up @@ -54,7 +55,8 @@ export function toggleState(
}

if (!CustomStatesSetSupported) {
elementInternals.shadowRoot!.host.toggleAttribute(`state--${state}`, force);
// Attribute mutations must wait until a custom element's constructor has returned.
Updates.enqueue(() => elementInternals.shadowRoot!.host.toggleAttribute(`state--${state}`, force));
return;
}
if (force ?? !elementInternals.states.has(state)) {
Expand Down