| |
| <!DOCTYPE html> |
| <title>Element.matchContainer</title> |
| <link rel="help" href="https://github.com/w3c/csswg-drafts/pull/13551"> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <style> |
| .size-container { |
| container-type: inline-size; |
| } |
| .style-container { |
| container-name: style-container; |
| } |
| </style> |
| <div id="root"></div> |
| <script> |
| function createTestElement(html) { |
| document.getElementById("root").innerHTML = html; |
| return document.getElementById("root").firstElementChild; |
| } |
| test(() => { |
| let container = createTestElement(` |
| <div class="size-container" style="width: 200px"> |
| <div id="target"></div> |
| </div> |
| `); |
| let target = container.querySelector("#target"); |
| let qMatches = target.matchContainer("(width: 200px)"); |
| assert_true(qMatches.matches, "Exact width should match."); |
| let qLess = target.matchContainer("(width < 100px)"); |
| assert_false(qLess.matches, "Target width is 200px, so < 100px should not match."); |
| let qGreater = target.matchContainer("(width > 100px)"); |
| assert_true(qGreater.matches, "Target width is 200px, so > 100px should match."); |
| }, "Element.matchContainer correctly matches the size of its container query."); |
| |
| test(() => { |
| let container = createTestElement(` |
| <div class="size-container" style="width: 200px"> |
| <div id="target"></div> |
| </div> |
| `); |
| let target = container.querySelector("#target"); |
| let invalidContainerQuery = target.matchContainer("(invalid-feature)"); |
| assert_false(invalidContainerQuery.matches, |
| "matchContainer with invalid query should not match."); |
| }, "Element.matchContainer handles an invalid query."); |
| test(() => { |
| let container = createTestElement(` |
| <div class="size-container" style="width: 200px"> |
| <div id="target"></div> |
| </div> |
| `); |
| let target = container.querySelector("#target"); |
| let syntaxError = target.matchContainer("???"); |
| assert_false(syntaxError.matches, |
| "matchContainer with syntax error should not match."); |
| let emptyQuery = target.matchContainer(""); |
| assert_false(emptyQuery.matches, |
| "matchContainer with empty string should not match."); |
| }, "Element.matchContainer handles syntax errors and empty queries."); |
| |
| test(() => { |
| let container = createTestElement(` |
| <div class="size-container" style="width: 200px"> |
| <div id="target"></div> |
| </div> |
| `); |
| let target = container.querySelector("#target"); |
| let q = target.matchContainer("(width > 100px)"); |
| assert_true(q.matches, "Initially matches at 200px."); |
| container.style.width = "50px"; |
| assert_false(q.matches, "Should flip to false after shrinking below 100px."); |
| container.style.width = "300px"; |
| assert_true(q.matches, "Should flip back to true after growing above 100px."); |
| }, "Element.matchContainer .matches reflects container size changes dynamically."); |
| |
| test(() => { |
| let container = createTestElement(` |
| <div class="size-container" id="displaycase" style="width: 200px"> |
| <div id="target"></div> |
| </div> |
| `); |
| let target = container.querySelector("#target"); |
| let q = target.matchContainer("(width > 100px)"); |
| assert_true(q.matches, "Initially matches."); |
| target.style.display = "none"; |
| assert_true(q.matches, |
| "display:none target still resolves to its container; matches the " + |
| "container's preserved width. (Mirrors @container rule semantics, " + |
| "which evaluate for display:none descendants — required for the " + |
| "@container { display: none } toggle pattern to work.)"); |
| target.style.display = "block"; |
| assert_true(q.matches, "Still matches after re-display."); |
| }, "Element.matchContainer .matches still evaluates when target is display:none."); |
| |
| test(() => { |
| let container = createTestElement(` |
| <div class="size-container" style="width: 200px"> |
| <div id="target"></div> |
| </div> |
| `); |
| let target = container.querySelector("#target"); |
| let q = target.matchContainer("(width > 100px)"); |
| assert_true(q.matches, "Initially matches while in tree."); |
| target.remove(); |
| assert_false(q.matches, |
| "Removed target has no container; should not match."); |
| container.appendChild(target); |
| assert_true(q.matches, "Re-inserted target should match again."); |
| }, "Element.matchContainer .matches updates when the target is removed from and re-inserted into the dom tree."); |
| |
| test(() => { |
| let wrapper = createTestElement(` |
| <div> |
| <div class="size-container" style="width: 200px"> |
| <div id="hidden" style="display: none"> |
| <div id="middle"> |
| <div id="target"></div> |
| </div> |
| </div> |
| </div> |
| <div id="narrow" class="size-container" style="width: 50px"></div> |
| </div> |
| `); |
| let hidden = wrapper.querySelector("#hidden"); |
| let middle = wrapper.querySelector("#middle"); |
| let target = wrapper.querySelector("#target"); |
| let narrow = wrapper.querySelector("#narrow"); |
| // Forces computed styles on #target and its hidden ancestors. |
| getComputedStyle(target).width; |
| let q = target.matchContainer("(width > 100px)"); |
| assert_true(q.matches, "Resolves to the 200px container while hidden."); |
| // Changes an ancestor's style inside the hidden subtree. |
| middle.style.color = "red"; |
| // Updates style before the move. |
| document.body.offsetWidth; |
| // Re-parenting |
| narrow.appendChild(hidden); |
| // Forces computed styles again in the new location. |
| getComputedStyle(target).width; |
| assert_false(q.matches, |
| "Resolves to the 50px container after the hidden subtree is moved."); |
| }, "Element.matchContainer .matches follows a hidden subtree with a forced computed style when it is moved to another container."); |
| |
| test(() => { |
| let wrapper = createTestElement(` |
| <div> |
| <div class="size-container" style="width: 200px"> |
| <div id="mv"> |
| <div id="target"></div> |
| </div> |
| </div> |
| <div id="narrow" class="size-container" style="width: 50px"></div> |
| </div> |
| `); |
| let mv = wrapper.querySelector("#mv"); |
| let target = wrapper.querySelector("#target"); |
| let narrow = wrapper.querySelector("#narrow"); |
| let q = target.matchContainer("(width > 100px)"); |
| assert_true(q.matches, "Initially resolves to the 200px container."); |
| narrow.moveBefore(mv, null); |
| assert_false(q.matches, |
| "Resolves to the 50px container after moveBefore()."); |
| }, "Element.matchContainer .matches updates when an ancestor is moved with moveBefore()."); |
| |
| test(() => { |
| let container = createTestElement(` |
| <div class="style-container" style="--my-prop: foo"> |
| <div id="target"></div> |
| </div> |
| `); |
| let target = container.querySelector("#target"); |
| assert_true(target.matchContainer("style(--my-prop: foo)").matches, |
| "Style query for matching custom property should match."); |
| assert_false(target.matchContainer("style(--my-prop: bar)").matches, |
| "Style query for non-matching custom property should not match."); |
| assert_false(target.matchContainer("style(--missing: foo)").matches, |
| "Style query for unset custom property should not match."); |
| }, "Element.matchContainer matches style queries on a style container."); |
| |
| test(() => { |
| let container = createTestElement(` |
| <div class="style-container" style="--my-prop: foo"> |
| <div id="target"></div> |
| </div> |
| `); |
| let target = container.querySelector("#target"); |
| let q = target.matchContainer("style(--my-prop: foo)"); |
| assert_true(q.matches, "Initially matches."); |
| container.style.setProperty("--my-prop", "bar"); |
| assert_false(q.matches, "Should flip to false after style change."); |
| container.style.setProperty("--my-prop", "foo"); |
| assert_true(q.matches, "Should flip back to true after restoring value."); |
| }, "Element.matchContainer .matches reflects custom property changes on the style container dynamically."); |
| |
| test(() => { |
| let container = createTestElement(` |
| <div class="size-container" style="width: 200px"> |
| <div id="target"></div> |
| </div> |
| `); |
| let target = container.querySelector("#target"); |
| assert_true(target.matchContainer("(width > 100px), (width < 50px)").matches, |
| "Matches when the first query in the list matches."); |
| assert_true(target.matchContainer("(width < 50px), (width > 100px)").matches, |
| "Matches when the second query in the list matches."); |
| assert_true(target.matchContainer("(width > 100px), (width: 200px)").matches, |
| "Matches when both queries in the list match."); |
| assert_false(target.matchContainer("(width < 50px), (width < 60px)").matches, |
| "Does not match when no query in the list matches."); |
| }, "Element.matchContainer matches a comma-separated list if any query matches."); |
| |
| const serializationCases = [ |
| ["(width > 100px)", "(width > 100px)"], |
| ["(width < 300px)", "(width < 300px)"], |
| ["(min-width: 300px)", "(min-width: 300px)"], |
| ["(not (max-width: 500px))", "(not (max-width: 500px))"], |
| ["((max-width: 500px) and (max-height: 500px))", |
| "((max-width: 500px) and (max-height: 500px))"], |
| ["((max-width: 500px) or (max-height: 500px))", |
| "((max-width: 500px) or (max-height: 500px))"], |
| ["(width) and (height)", "(width) and (height)"], |
| ["(width) or (height)", "(width) or (height)"], |
| ["somename not (width)", "somename not (width)"], |
| ["test_name (width) or (height)", "test_name (width) or (height)"], |
| ["style(--my-prop: foo)", "style(--my-prop: foo)"], |
| ["(min-width:300px)", "(min-width: 300px)"], |
| ["(min-width: 300px)", "(min-width: 300px)"], |
| ["(MIN-WIDTH: 300px)", "(min-width: 300px)"], |
| ["(width<300px)", "(width < 300px)"], |
| ["(width > 100px), (width < 50px)", "(width > 100px), (width < 50px)"], |
| ["(width < 300px),(min-width: 300px)", "(width < 300px), (min-width: 300px)"], |
| ]; |
| for (const [input, expected] of serializationCases) { |
| test(() => { |
| let container = createTestElement(` |
| <div class="size-container" style="width: 200px"> |
| <div id="target"></div> |
| </div> |
| `); |
| let target = container.querySelector("#target"); |
| assert_equals(target.matchContainer(input).query, expected); |
| }, `Element.matchContainer .query serializes ${JSON.stringify(input)}`); |
| } |
| |
| test(() => { |
| let container = createTestElement(` |
| <div class="size-container" style="width: 200px"> |
| <div id="target"></div> |
| </div> |
| `); |
| let target = container.querySelector("#target"); |
| assert_equals(target.matchContainer("???").query, "", |
| "query is empty for a syntax error."); |
| assert_equals(target.matchContainer("").query, "", |
| "query is empty for an empty query."); |
| assert_equals(target.matchContainer("(width > 100px),").query, "", |
| "query is empty for a trailing comma in the list."); |
| assert_equals(target.matchContainer(",(width > 100px)").query, "", |
| "query is empty for a leading comma in the list."); |
| }, "Element.matchContainer .query is empty for invalid queries."); |
| </script> |