<!DOCTYPE html>
<title>CSS Anchor Position Test: invalid at computed-value time</title>
<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#anchor-valid">
<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#anchor-size-valid">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<style>
:root {
--top: top;
}
#cb {
position: relative;
width: 200px;
height: 200px;
border: 1px solid black;
}
#anchor {
anchor-name: --a;
position: absolute;
width: 50px;
height: 40px;
left: 75px;
top: 75px;
background: coral;
}
#main > div, #ref {
position: absolute;
background: seagreen;
}
#ref {
inset: unset;
width: unset;
height: unset;
min-width: unset;
min-height: unset;
max-width: unset;
max-height: unset;
}
</style>
<div id=cb>
<div id=anchor></div>
<div id=main></div>
<div id=ref>X</div>
</div>
<script>
// Append <div>X</div> to `container`, and remove it again once the test (`t`)
// is finished.
function createTarget(t, container) {
t.add_cleanup(() => { container.replaceChildren(); });
let target = document.createElement('div');
target.textContent = 'X';
container.append(target);
return target;
}
// First, some sanity checks to verify that the anchor etc is set up correctly,
// and that anchor() queries can produce results if done correctly.
test((t) => {
let target = createTarget(t, main);
target.style = `
position-anchor: --a;
left:anchor(right);
top:anchor(top);
width:anchor-size(width);
height:anchor-size(height);
`;
let cs = getComputedStyle(target);
assert_equals(cs.left, '125px');
assert_equals(cs.top, '75px');
assert_equals(cs.width, '50px');
assert_equals(cs.height, '40px');
}, 'Element can be anchor positioned');
test((t) => {
let target = createTarget(t, main);
target.style = `
/* No position-anchor here */
left:anchor(right, 17px);
top:anchor(top, 18px);
width:anchor-size(width, 42px);
height:anchor-size(height, 43px);
`;
let cs = getComputedStyle(target);
assert_equals(cs.left, '17px');
assert_equals(cs.top, '18px');
assert_equals(cs.width, '42px');
assert_equals(cs.height, '43px');
}, 'Element can use <length> fallback if present');
// Now test that any invalid anchor*() behaves as invalid at computed-value
// time if there's no fallback specified.
// Check that an anchored element with the specified style has the same
// computed insets and sizing as the reference element (#ref), i.e. all
// insets and sizing properties behave as 'unset'.
function test_ref(style, description) {
test((t) => {
let target = createTarget(t, main);
target.style = style;
let cs = getComputedStyle(target);
let ref_cs = getComputedStyle(ref);
assert_equals(cs.top, ref_cs.top, 'top');
assert_equals(cs.left, ref_cs.left, 'left');
assert_equals(cs.right, ref_cs.right, 'right');
assert_equals(cs.bottom, ref_cs.bottom, 'bottom');
assert_equals(cs.width, ref_cs.width, 'width');
assert_equals(cs.height, ref_cs.height, 'height');
assert_equals(cs.minWidth, ref_cs.minWidth, 'minWidth');
assert_equals(cs.minHeight, ref_cs.minHeight, 'minHeight');
assert_equals(cs.maxWidth, ref_cs.maxWidth, 'maxWidth');
assert_equals(cs.maxHeight, ref_cs.maxHeight, 'maxHeight');
}, `Invalid anchor function, ${description}`);
}
// No default anchor (position-anchor):
test_ref('left:anchor(left)', 'left');
test_ref('right:anchor(right)', 'right');
test_ref('bottom:anchor(bottom)', 'bottom');
test_ref('top:anchor(top)', 'top');
test_ref('width:anchor-size(width)', 'width');
test_ref('height:anchor-size(height)', 'height');
test_ref('min-width:anchor-size(width)', 'min-width');
test_ref('min-height:anchor-size(height)', 'min-height');
test_ref('max-width:anchor-size(width)', 'max-width');
test_ref('max-height:anchor-size(height)', 'max-height');
// Unknown anchor reference:
test_ref('left:anchor(--unknown left)', '--unknown left');
test_ref('width:anchor-size(--unknown width)', '--unknown width');
// Wrong axis;
test_ref('left:anchor(--a top)', 'cross-axis query (vertical)');
test_ref('top:anchor(--a left)', ' cross-axis query (horizontal)');
// Wrong query for the given property:
test_ref('top:anchor-size(--a width)', 'anchor-size() in inset');
test_ref('width:anchor(--a left)', 'anchor() in sizing property');
// Invalid anchor*() deeper within calc():
test_ref('left:calc(anchor(left) + 10px)', 'nested left');
test_ref('right:calc(anchor(right) + 10px)', 'nested right');
test_ref('bottom:calc(anchor(bottom) + 10px)', 'nested bottom');
test_ref('top:calc(anchor(top) + 10px)', 'nested top');
test_ref('min-width:calc(anchor-size(width) + 10px)', 'nested min-width');
test_ref('min-height:calc(anchor-size(height) + 10px)', 'nested min-height');
test_ref('max-width:calc(anchor-size(width) + 10px)', 'nested max-width');
test_ref('max-height:calc(anchor-size(height) + 10px)', 'nested max-height');
// Invalid anchor*() within fallback:
test_ref('top:anchor(top, anchor(--unknown top))', 'invalid anchor() in fallback');
test_ref('width:anchor-size(width, anchor-size(--unknown width))', 'invalid anchor-size() in fallback');
// Non-calc() functions:
test_ref('top:min(10px, anchor(top))', 'min()');
test_ref('top:max(10px, anchor(top))', 'max()');
test_ref('top:abs(anchor(top) - 100px)', 'abs()');
test_ref('top:calc(sign(anchor(top) - 100px) * 20px)', 'sign()');
// var():
test_ref('top:anchor(var(--top))', 'anchor(var())');
test_ref('top:anchor(var(--unknown, top))', 'anchor(unknown var()) (fallback)');
test_ref('top:anchor(var(--unknown))', 'anchor(unknown var()) (no fallback)');
// Reverting to an invalid anchor():
test((t) => {
let target = createTarget(t, main);
target.setAttribute('id', 'target');
let css = document.createElement('style');
css.textContent = `
@layer base {
#target {
top: anchor(top); /* Invalid */
color: green;
}
}
#target {
top: revert-layer; /* Reverts to 'base'. */
}
`;
t.add_cleanup(() => { css.remove(); })
cb.append(css);
let cs = getComputedStyle(target);
let ref_cs = getComputedStyle(ref);
// The color check verifies that the rule is applied at all.
assert_equals(cs.color, 'rgb(0, 128, 0)');
assert_equals(cs.top, ref_cs.top);
}, 'Revert to invalid anchor()');
// Using <try-tactic> to flip to an invalid anchor():
test((t) => {
let target = createTarget(t, main);
target.setAttribute('id', 'target');
let css = document.createElement('style');
css.textContent = `
@position-try --pt {
/* Undo force overflow, and also use this value to check that
the rule is applied at all. */
left: 10px;
/* Invalid. Becomes bottom:anchor(bottom) (also invalid)
after flip-block. */
top: anchor(top);
}
#target {
left: 9999px; /* Force overflow. */
position-try-fallbacks: --pt flip-block;
}
`;
t.add_cleanup(() => { css.remove(); })
cb.append(css);
let cs = getComputedStyle(target);
let ref_cs = getComputedStyle(ref);
assert_equals(cs.left, '10px', 'left');
// 'right' is not important in this test.
assert_equals(cs.top, ref_cs.top, 'top');
assert_equals(cs.bottom, ref_cs.bottom, 'bottom');
}, 'Flip to invalid anchor()');
</script>