diff --git a/src/parse.ts b/src/parse.ts index 71cc8bfb..4c389088 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -316,11 +316,14 @@ function getPositionFallbackRules(node: csstree.CssNode) { return {}; } -function getCSSPropertyValue(el: HTMLElement, prop: string) { +export function getCSSPropertyValue(el: HTMLElement, prop: string) { return getComputedStyle(el).getPropertyValue(prop).trim(); } -function getAnchorEl(targetEl: HTMLElement | null, anchorObj: AnchorFunction) { +async function getAnchorEl( + targetEl: HTMLElement | null, + anchorObj: AnchorFunction, +) { let anchorName = anchorObj.anchorName; const customPropName = anchorObj.customPropName; if (targetEl && !anchorName) { @@ -328,11 +331,11 @@ function getAnchorEl(targetEl: HTMLElement | null, anchorObj: AnchorFunction) { if (customPropName) { anchorName = getCSSPropertyValue(targetEl, customPropName); } else if (anchorAttr) { - return validatedForPositioning(targetEl, [`#${anchorAttr}`]); + return await validatedForPositioning(targetEl, [`#${anchorAttr}`]); } } const anchorSelectors = anchorName ? anchorNames[anchorName] ?? [] : []; - return validatedForPositioning(targetEl, anchorSelectors); + return await validatedForPositioning(targetEl, anchorSelectors); } function getAST(cssText: string) { @@ -345,7 +348,7 @@ function getAST(cssText: string) { return ast; } -export function parseCSS(styleData: StyleData[]) { +export async function parseCSS(styleData: StyleData[]) { const anchorFunctions: AnchorFunctionDeclarations = {}; const fallbackNames: FallbackNames = {}; const fallbacks: Fallbacks = {}; @@ -739,14 +742,17 @@ export function parseCSS(styleData: StyleData[]) { if (positionFallbacks) { const targetEl: HTMLElement | null = document.querySelector(targetSel); // Populate `anchorEl` for each fallback `anchor()` fn - positionFallbacks.forEach((tryBlock) => { + for (const tryBlock of positionFallbacks) { for (const [prop, value] of Object.entries(tryBlock)) { if (typeof value === 'object') { - const anchorEl = getAnchorEl(targetEl, value as AnchorFunction); + const anchorEl = await getAnchorEl( + targetEl, + value as AnchorFunction, + ); (tryBlock[prop] as AnchorFunction).anchorEl = anchorEl; } } - }); + } validPositions[targetSel] = { fallbacks: positionFallbacks, }; @@ -758,7 +764,7 @@ export function parseCSS(styleData: StyleData[]) { const targetEl: HTMLElement | null = document.querySelector(targetSel); for (const [targetProperty, anchorObjects] of Object.entries(anchorFns)) { for (const anchorObj of anchorObjects) { - const anchorEl = getAnchorEl(targetEl, anchorObj); + const anchorEl = await getAnchorEl(targetEl, anchorObj); // Populate `anchorEl` for each `anchor()` fn validPositions[targetSel] = { ...validPositions[targetSel], diff --git a/src/polyfill.ts b/src/polyfill.ts index a9124589..84a3800e 100644 --- a/src/polyfill.ts +++ b/src/polyfill.ts @@ -174,7 +174,7 @@ export async function polyfill() { const styleData = await fetchCSS(); // parse CSS - const rules = parseCSS(styleData); + const rules = await parseCSS(styleData); if (Object.values(rules).length) { // calculate position values diff --git a/src/validate.ts b/src/validate.ts index 214cb727..45c3dac0 100644 --- a/src/validate.ts +++ b/src/validate.ts @@ -1,72 +1,64 @@ -// Given a target element and CSS selector(s) for potential anchor element(s), -// returns the first element that passes validation, -// or `null` if no valid anchor element is found -export function validatedForPositioning( - targetEl: HTMLElement | null, - anchorSelectors: string[], -) { - if (!targetEl || anchorSelectors.length === 0) { - return null; - } +import { platform } from '@floating-ui/dom'; - const anchorElements: NodeListOf = document.querySelectorAll( - anchorSelectors.join(', '), - ); +import { getCSSPropertyValue } from './parse.js'; - for (const anchor of anchorElements) { - if (isValidAnchorElement(anchor, targetEl)) { - return anchor; - } +// Given an element and CSS style property, +// checks if the CSS property equals a certain value +function hasStyle(element: HTMLElement, cssProperty: string, value: string) { + return getCSSPropertyValue(element, cssProperty) === value; +} + +// Given a target element's containing block (CB) and an anchor element, +// determines if the anchor element is a descendant of the target CB. +// An additional check is added to see if the target CB is the anchor, +// because `.contains()` will return true: "a node is contained inside itself." +// https://developer.mozilla.org/en-US/docs/Web/API/Node/contains +function isContainingBlockDescendant( + containingBlock: Element | Window | undefined, + anchor: Element, +): boolean { + if (!containingBlock || containingBlock === anchor) { + return false; } - return null; + if (containingBlock === window) { + return (containingBlock as Window).document.contains(anchor); + } else { + return (containingBlock as HTMLElement).contains(anchor); + } } -export function isAbsolutelyPositioned(el?: HTMLElement | null) { - return Boolean( - el && - (el.style.position === 'absolute' || - getComputedStyle(el).position === 'absolute'), - ); +// Determines whether the containing block (CB) of the element +// is the initial containing block (ICB) +export function isContainingBlockICB( + targetContainingBlock: Element | Window | undefined, +) { + return Boolean(targetContainingBlock === window); } -export function hasDisplayNone(el?: HTMLElement | null) { - return Boolean( - el && - (el.style.display === 'none' || getComputedStyle(el).display === 'none'), - ); +export function isFixedPositioned(el: HTMLElement) { + return hasStyle(el, 'position', 'fixed'); } -// Determines whether the containing block (CB) of the element -// is the initial containing block (ICB): -// - `offsetParent` returns `null` when the CB is the ICB, -// except in Firefox where `offsetParent` returns the `body` element -// - Excludes elements when they or their parents have `display: none` -export function isContainingBlockICB(targetElement: HTMLElement) { - const isDisplayNone = - hasDisplayNone(targetElement) || - hasDisplayNone(targetElement.parentElement); - - const cbIsBodyElementFromFF = - targetElement.offsetParent === document.querySelector('body') && - navigator.userAgent.includes('Firefox'); - - const offsetParentNullOrBody = - targetElement.offsetParent === null || cbIsBodyElementFromFF; - - if (offsetParentNullOrBody && !isDisplayNone) { - return true; - } - return false; +export function isAbsolutelyPositioned(el?: HTMLElement | null) { + return Boolean( + el && (isFixedPositioned(el) || hasStyle(el, 'position', 'absolute')), + ); } // Validates that anchor element is a valid anchor for given target element -export function isValidAnchorElement(anchor: HTMLElement, target: HTMLElement) { +export async function isValidAnchorElement( + anchor: HTMLElement, + target: HTMLElement, +) { + const anchorContainingBlock = await platform.getOffsetParent?.(anchor); + const targetContainingBlock = await platform.getOffsetParent?.(target); + // If el has the same containing block as the querying element, - // el must not be absolutely positioned: + // el must not be absolutely positioned. if ( isAbsolutelyPositioned(anchor) && - anchor.offsetParent === target.offsetParent + anchorContainingBlock === targetContainingBlock ) { return false; } @@ -75,31 +67,59 @@ export function isValidAnchorElement(anchor: HTMLElement, target: HTMLElement) { // the last containing block in el's containing block chain // before reaching the querying element's containing block // must not be absolutely positioned: - if (anchor.offsetParent !== target.offsetParent) { - let currentCB: HTMLElement | null; - const anchorCBchain: HTMLElement[] = []; - - currentCB = anchor.offsetParent as HTMLElement | null; - while (currentCB && currentCB !== target.offsetParent) { + if (anchorContainingBlock !== targetContainingBlock) { + let currentCB: Element | Window | undefined; + const anchorCBchain: typeof currentCB[] = []; + + currentCB = anchorContainingBlock; + while ( + currentCB && + currentCB !== targetContainingBlock && + currentCB !== window + ) { anchorCBchain.push(currentCB); - currentCB = currentCB.offsetParent as HTMLElement | null; + currentCB = await platform.getOffsetParent?.(currentCB as HTMLElement); } - const lastInChain = anchorCBchain[anchorCBchain.length - 1]; - if (isAbsolutelyPositioned(lastInChain)) { + + if (lastInChain && isAbsolutelyPositioned(lastInChain as HTMLElement)) { return false; } } - // Either el must be a descendant of the querying element's containing block, - // or the querying element's containing block must be - // the initial containing block: - const isDescendant = Boolean(target.offsetParent?.contains(anchor)); - const targetCBIsInitialCB = isContainingBlockICB(target); - - if (isDescendant || targetCBIsInitialCB) { + // Either anchor el is a descendant of query el’s containing block, + // or query el’s containing block is the initial containing block + // https://drafts4.csswg.org/css-anchor-1/#determining + if ( + isContainingBlockDescendant(targetContainingBlock, anchor) || + isContainingBlockICB(targetContainingBlock) + ) { return true; } return false; } + +// Given a target element and CSS selector(s) for potential anchor element(s), +// returns the first element that passes validation, +// or `null` if no valid anchor element is found +export async function validatedForPositioning( + targetEl: HTMLElement | null, + anchorSelectors: string[], +) { + if (!targetEl || anchorSelectors.length === 0) { + return null; + } + + const anchorElements: NodeListOf = document.querySelectorAll( + anchorSelectors.join(', '), + ); + + for (const anchor of anchorElements) { + if (await isValidAnchorElement(anchor, targetEl)) { + return anchor; + } + } + + return null; +} diff --git a/tests/e2e/validate.test.ts b/tests/e2e/validate.test.ts index 68fb1ea4..4674a0e8 100644 --- a/tests/e2e/validate.test.ts +++ b/tests/e2e/validate.test.ts @@ -1,66 +1,88 @@ -import { type Page, expect, test } from '@playwright/test'; - -import { - hasDisplayNone, - isAbsolutelyPositioned, - isContainingBlockICB, - isValidAnchorElement, - validatedForPositioning, -} from '../../src/validate.js'; - -let sharedPage: Page; -test.beforeAll(async ({ browser }) => { - sharedPage = await browser.newPage(); - await sharedPage.goto('/'); - await sharedPage.addScriptTag({ - content: `${isAbsolutelyPositioned}\n${isValidAnchorElement}\n${isContainingBlockICB}\n${hasDisplayNone}`, +import { type Page, Browser, expect, test } from '@playwright/test'; + +async function buildPage(browser: Browser) { + const page = await browser.newPage(); + await page.goto('/'); + await page.addScriptTag({ + type: 'module', + + content: ` + import { + isValidAnchorElement, + validatedForPositioning, + } from '../../src/validate.ts'; + + window.isValidAnchorElement = isValidAnchorElement + window.validatedForPositioning = validatedForPositioning + `, }); -}); -test.afterAll(async () => { - await sharedPage.close(); + let loading = true; + while (loading) { + loading = await page.evaluate(() => { + document.getSelection('script'); + return window.isValidAnchorElement === undefined; + }); + } + + return page; +} + +test.afterAll(async ({ browser }) => { + await browser.close(); }); const anchorSelector = '#my-anchor-positioning'; const targetSelector = '#my-target-positioning'; -async function callValidFunction(sharedPage: Page) { - return await sharedPage.evaluate( - ([anchorSelector, targetSelector]) => { - const targetElement = document.querySelector( - targetSelector, - ) as HTMLElement; - const anchorElement = document.querySelector( - anchorSelector, - ) as HTMLElement; - return isValidAnchorElement(anchorElement, targetElement); - }, - [anchorSelector, targetSelector], - ); +async function callValidFunction(page: Page) { + try { + return await page.evaluate( + async ([anchorSelector, targetSelector]) => { + const targetElement = document.querySelector( + targetSelector, + ) as HTMLElement; + const anchorElement = document.querySelector( + anchorSelector, + ) as HTMLElement; + return await isValidAnchorElement(anchorElement, targetElement); + }, + [anchorSelector, targetSelector], + ); + } catch (e) { + await page.close(); + throw e; + } } // el is a descendant of the querying element’s containing block, // or the querying element’s containing block is the initial containing block -test("anchor is valid when it's is a descendant of the query element CB", async () => { - await sharedPage.setContent( +test("anchor is valid when it's is a descendant of the query element CB", async ({ + browser, +}) => { + const page = await buildPage(browser); + await page.setContent( `
Anchor
Target
`, - { waitUntil: 'domcontentloaded' }, + { waitUntil: 'load' }, ); - const result = await callValidFunction(sharedPage); - + const result = await callValidFunction(page); + await page.close(); expect(result).toBe(true); }); // el is a descendant of the querying element’s containing block, // or the querying element’s containing block is the initial containing block -test("anchor is valid if it's not descendant of query element CB but query element CB is ICB", async () => { - await sharedPage.setContent( +test("anchor is valid if it's not descendant of query element CB but query element CB is ICB", async ({ + browser, +}) => { + const page = await buildPage(browser); + await page.setContent( `
Anchor
@@ -71,48 +93,58 @@ test("anchor is valid if it's not descendant of query element CB but query eleme
Target
`, - { waitUntil: 'domcontentloaded' }, + { waitUntil: 'load' }, ); - const result = await callValidFunction(sharedPage); - + const result = await callValidFunction(page); + await page.close(); expect(result).toBe(true); }); -test("anchor is valid if it's not descendant of query element CB and query element CB is the ICB - position: fixed", async () => { - await sharedPage.setContent( +test("anchor is valid if it's not descendant of query element CB and query element CB is the ICB - position: fixed", async ({ + browser, +}) => { + const page = await buildPage(browser); + // position: fixed is added to targe to simulate ICB on target + await page.setContent( `
Target
Anchor
`, - { waitUntil: 'domcontentloaded' }, + { waitUntil: 'load' }, ); - const result = await callValidFunction(sharedPage); - + const result = await callValidFunction(page); + await page.close(); expect(result).toBe(true); }); -test('anchor is valid if it is not descendant of query element CB and query element CB is the ICB - no positioned ancestor', async () => { - await sharedPage.setContent( +test('anchor is valid if it is not descendant of query element CB and query element CB is the ICB - no positioned ancestor', async ({ + browser, +}) => { + const page = await buildPage(browser); + await page.setContent( `
Target
Anchor
`, - { waitUntil: 'domcontentloaded' }, + { waitUntil: 'load' }, ); - const result = await callValidFunction(sharedPage); - + const result = await callValidFunction(page); + await page.close(); expect(result).toBe(true); }); -test("anchor is NOT valid if it's not descendant of query element CB AND query element CB is not ICB", async () => { - await sharedPage.setContent( +test("anchor is NOT valid if it's not descendant of query element CB AND query element CB is not ICB", async ({ + browser, +}) => { + const page = await buildPage(browser); + await page.setContent( `
Anchor
@@ -127,61 +159,70 @@ test("anchor is NOT valid if it's not descendant of query element CB AND query e
Target
`, - { waitUntil: 'domcontentloaded' }, + { waitUntil: 'load' }, ); - const result = await callValidFunction(sharedPage); - - expect(result).toBe(false); -}); - -test("anchor is NOT valid if it's not descendant of query element CB AND query element CB is not ICB - display:none", async () => { - await sharedPage.setContent( - ` - -
Anchor
- `, - { waitUntil: 'domcontentloaded' }, - ); - - const result = await callValidFunction(sharedPage); - + const result = await callValidFunction(page); + await page.close(); expect(result).toBe(false); }); // if el has the same containing block as the querying element, // el is not absolutely positioned -test('anchor is valid when anchor has same CB as querying element and anchor is not absolutely positioned', async () => { - await sharedPage.setContent( +test('anchor is valid when anchor has same CB as querying element and anchor is not absolutely positioned', async ({ + browser, +}) => { + const page = await buildPage(browser); + await page.setContent( `
Anchor
Target
; `, - { waitUntil: 'domcontentloaded' }, + { waitUntil: 'load' }, ); - const result = await callValidFunction(sharedPage); - + const result = await callValidFunction(page); + await page.close(); expect(result).toBe(true); }); -test('anchor is NOT valid when anchor has same CB as querying element, but anchor is absolutely positioned', async () => { - await sharedPage.setContent( +test('anchor is NOT valid when anchor has same CB as querying element, but anchor is absolutely positioned', async ({ + browser, +}) => { + const page = await buildPage(browser); + await page.setContent( `
Anchor
Target
; `, - { waitUntil: 'domcontentloaded' }, + { waitUntil: 'load' }, ); - const result = await callValidFunction(sharedPage); + const result = await callValidFunction(page); + await page.close(); + expect(result).toBe(false); +}); + +test('anchor is NOT valid when anchor has same CB as querying element, but anchor is absolutely positioned - fixed position', async ({ + browser, +}) => { + const page = await buildPage(browser); + await page.setContent( + ` +
+
Anchor
+
Target
+
; + `, + { waitUntil: 'load' }, + ); + const result = await callValidFunction(page); + await page.close(); expect(result).toBe(false); }); @@ -189,68 +230,53 @@ test('anchor is NOT valid when anchor has same CB as querying element, but ancho // the last containing block in el’s containing block chain // before reaching the querying element’s containing block // is not absolutely positioned -test('anchor is valid if it has a different CB from the querying element, and the last CB in anchor CB chain block before the query element CB is not absolutely positioned', async () => { - // HTML from WPT: https://github.com/web-platform-tests/wpt/blob/master/css/css-anchor-position/anchor-name-002.tentative.html - await sharedPage.setContent( +test('anchor is valid if it has a different CB from the querying element, and the last CB in anchor CB chain block before the query element CB is not absolutely positioned', async ({ + browser, +}) => { + // HTML from WPT: https://github.com/web-platform-tests/wpt/blob/master/css/css-anchor-position/anchor-position-002.html + const page = await buildPage(browser); + await page.setContent( ` - - -
-
-
-
-
-
- -
+ +
+
+
+
+
Anchor
- -
+
Target
-
- `, - { waitUntil: 'domcontentloaded' }, + `, + { waitUntil: 'load' }, ); - const result = await callValidFunction(sharedPage); - + const result = await callValidFunction(page); + await page.close(); expect(result).toBe(true); }); -test('anchor is NOT valid if it has a different CB from the querying element, and the last CB in anchor CB chain before the query element CB is absolutely positioned', async () => { - // HTML from WPT: https://github.com/web-platform-tests/wpt/blob/master/css/css-anchor-position/anchor-name-002.tentative.html - await sharedPage.setContent( +test('anchor is NOT valid if it has a different CB from the querying element, and the last CB in anchor CB chain before the query element CB is absolutely positioned', async ({ + browser, +}) => { + // HTML from WPT: https://github.com/web-platform-tests/wpt/blob/master/css/css-anchor-position/anchor-name-002.html + const page = await buildPage(browser); + await page.setContent( ` + + +
+
+
+
+
+
+
+ + `, + { waitUntil: 'load' }, + ); + const valid = await callValidFunction(page); + + const validationResults = await page.evaluate( + async ([anchorSelector, targetSelector]) => { + interface Data { + results: { + anchor: HTMLElement | null; + }; + anchorWidth: string | undefined; + anchorText: string | undefined; + } + + const targetElement = document.querySelector( + targetSelector, + ) as HTMLElement; + + const validatedData = {} as Data; + const anchor = await validatedForPositioning(targetElement, [ + anchorSelector, + ]).then((value) => value); + + validatedData.results = { anchor }; + validatedData.anchorWidth = getComputedStyle(anchor).width; + + return validatedData; + }, + ['.anchor1', '.target9'], + ); + + await page.close(); + expect(valid).toBe(true); + expect(validationResults.results.anchor).toBeTruthy; + expect(validationResults.anchorWidth).toBe('9px'); +}); diff --git a/tests/unit/parse.test.ts b/tests/unit/parse.test.ts index 966f2ab7..4874c089 100644 --- a/tests/unit/parse.test.ts +++ b/tests/unit/parse.test.ts @@ -8,18 +8,23 @@ describe('parseCSS', () => { document.body.innerHTML = ''; }); - it('handles css with no `anchor()` fn', () => { - const result = parseCSS([{ css: sampleBaseCSS }] as StyleData[]); + it('handles css with no `anchor()` fn', async () => { + const result = await parseCSS([{ css: sampleBaseCSS }] as StyleData[]); expect(result).toEqual({}); }); - it('parses `anchor()` function', () => { - document.body.innerHTML = - '
'; + it('parses `anchor()` function', async () => { + document.body.innerHTML = ` +
+
Target
+
Anchor
+
+ `; const anchorEl = document.getElementById('my-anchor-positioning'); const css = getSampleCSS('anchor-positioning'); - const result = parseCSS([{ css }] as StyleData[]); + const result = await parseCSS([{ css }] as StyleData[]); + const expected = { '#my-target-positioning': { declarations: { @@ -48,13 +53,13 @@ describe('parseCSS', () => { expect(result).toEqual(expected); }); - it('parses `anchor()` (implicit name via `anchor` attr)', () => { + it('parses `anchor()` (implicit name via `anchor` attr)', async () => { document.body.innerHTML = - '
' + - '
'; + '
' + + '
'; const css = getSampleCSS('anchor-implicit'); document.head.innerHTML = ``; - const result = parseCSS([{ css }] as StyleData[]); + const result = await parseCSS([{ css }] as StyleData[]); const expected = { '#my-implicit-target': { declarations: { @@ -85,13 +90,13 @@ describe('parseCSS', () => { expect(result).toEqual(expected); }); - it('parses `anchor()` (name set via custom property)', () => { + it('parses `anchor()` (name set via custom property)', async () => { document.body.innerHTML = - '
' + - '
'; + '
' + + '
'; const css = getSampleCSS('anchor-name-custom-prop'); document.head.innerHTML = ``; - const result = parseCSS([{ css }] as StyleData[]); + const result = await parseCSS([{ css }] as StyleData[]); const expected = { '#my-target-name-prop': { declarations: { @@ -120,7 +125,7 @@ describe('parseCSS', () => { expect(result).toEqual(expected); }); - it('parses `anchor()` function with unknown anchor name', () => { + it('parses `anchor()` function with unknown anchor name', async () => { document.body.innerHTML = '
'; const css = ` #f1 { @@ -128,7 +133,7 @@ describe('parseCSS', () => { top: anchor(--my-anchor bottom); } `; - const result = parseCSS([{ css }] as StyleData[]); + const result = await parseCSS([{ css }] as StyleData[]); const expected = { '#f1': { declarations: { @@ -148,8 +153,9 @@ describe('parseCSS', () => { expect(result).toEqual(expected); }); - it('handles duplicate anchor-names', () => { - document.body.innerHTML = '
'; + it('handles duplicate anchor-names', async () => { + document.body.innerHTML = + '
'; const anchorEl = document.getElementById('a2'); const css = ` #a1 { @@ -163,7 +169,7 @@ describe('parseCSS', () => { top: anchor(--my-anchor bottom); } `; - const result = parseCSS([{ css }] as StyleData[]); + const result = await parseCSS([{ css }] as StyleData[]); const expected = { '#f1': { declarations: { @@ -183,12 +189,12 @@ describe('parseCSS', () => { expect(result).toEqual(expected); }); - it('parses `anchor()` function (custom properties)', () => { + it('parses `anchor()` function (custom properties)', async () => { document.body.innerHTML = - '
'; + '
'; const css = getSampleCSS('anchor'); document.head.innerHTML = ``; - const result = parseCSS([{ css }] as StyleData[]); + const result = await parseCSS([{ css }] as StyleData[]); const expected = { '#my-target': { declarations: { @@ -217,12 +223,12 @@ describe('parseCSS', () => { expect(result).toEqual(expected); }); - it('parses `anchor()` function (custom property passed through)', () => { + it('parses `anchor()` function (custom property passed through)', async () => { document.body.innerHTML = - '
'; + '
'; const css = getSampleCSS('anchor-custom-props'); document.head.innerHTML = ``; - const result = parseCSS([{ css }] as StyleData[]); + const result = await parseCSS([{ css }] as StyleData[]); const expected = { '#my-target-props': { declarations: { @@ -251,12 +257,12 @@ describe('parseCSS', () => { expect(result).toEqual(expected); }); - it('parses `anchor()` function (multiple duplicate custom properties)', () => { + it('parses `anchor()` function (multiple duplicate custom properties)', async () => { document.body.innerHTML = - '
'; + '
'; const css = getSampleCSS('anchor-duplicate-custom-props'); document.head.innerHTML = ``; - const result = parseCSS([{ css }] as StyleData[]); + const result = await parseCSS([{ css }] as StyleData[]); const expected = { '#target-duplicate-custom-props': { declarations: { @@ -307,12 +313,12 @@ describe('parseCSS', () => { expect(result).toEqual(expected); }); - it('parses `anchor()` function (math)', () => { + it('parses `anchor()` function (math)', async () => { document.body.innerHTML = - '
'; + '
'; const css = getSampleCSS('anchor-math'); document.head.innerHTML = ``; - const result = parseCSS([{ css }] as StyleData[]); + const result = await parseCSS([{ css }] as StyleData[]); const expected = { '#my-target-math': { declarations: { @@ -341,12 +347,12 @@ describe('parseCSS', () => { expect(result).toEqual(expected); }); - it('parses `@position-fallback` strategy', () => { + it('parses `@position-fallback` strategy', async () => { document.body.innerHTML = - '
'; + '
'; const anchorEl = document.getElementById('my-anchor-fallback'); const css = getSampleCSS('position-fallback'); - const result = parseCSS([{ css }] as StyleData[]); + const result = await parseCSS([{ css }] as StyleData[]); const expected = { '#my-target-fallback': { declarations: { @@ -459,7 +465,7 @@ describe('parseCSS', () => { expect(result).toEqual(expected); }); - it('parses `@position-fallback` with unknown anchor name', () => { + it('parses `@position-fallback` with unknown anchor name', async () => { document.body.innerHTML = '
'; const css = ` #my-target-fallback { @@ -474,7 +480,8 @@ describe('parseCSS', () => { } } `; - const result = parseCSS([{ css }] as StyleData[]); + const result = await parseCSS([{ css }] as StyleData[]); + const expected = { '#my-target-fallback': { fallbacks: [ @@ -501,14 +508,14 @@ describe('parseCSS', () => { expect(result).toEqual(expected); }); - it('handles invalid/missing `position-fallback`', () => { + it('handles invalid/missing `position-fallback`', async () => { const css = ` #target { position: absolute; position-fallback: --fallback; } `; - const result = parseCSS([{ css }] as StyleData[]); + const result = await parseCSS([{ css }] as StyleData[]); expect(result).toEqual({}); });