From 9e0700b2f99ae5c97422e24e998913bee73f4a71 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 20 Sep 2026 19:31:20 +0000 Subject: [PATCH 1/2] test: add regression fixtures for shorthand/longhand merge Cover the #478 background-size wipe, the same-class #417 border case, and guards for !important, subset-before, and `all`. Co-authored-by: David --- .../compress/restructure.merge/issue-417.css | 18 ++++++++++++++++++ .../restructure.merge/issue-417.min.css | 1 + .../restructure.merge/issue-478-all.css | 17 +++++++++++++++++ .../restructure.merge/issue-478-all.min.css | 1 + .../restructure.merge/issue-478-important.css | 19 +++++++++++++++++++ .../issue-478-important.min.css | 1 + .../restructure.merge/issue-478-shared.css | 19 +++++++++++++++++++ .../issue-478-shared.min.css | 1 + .../issue-478-subset-before.css | 16 ++++++++++++++++ .../issue-478-subset-before.min.css | 1 + .../restructure.merge/issue-478-subset.css | 16 ++++++++++++++++ .../issue-478-subset.min.css | 1 + .../compress/restructure.merge/issue-478.css | 18 ++++++++++++++++++ .../restructure.merge/issue-478.min.css | 1 + 14 files changed, 130 insertions(+) create mode 100644 fixtures/compress/restructure.merge/issue-417.css create mode 100644 fixtures/compress/restructure.merge/issue-417.min.css create mode 100644 fixtures/compress/restructure.merge/issue-478-all.css create mode 100644 fixtures/compress/restructure.merge/issue-478-all.min.css create mode 100644 fixtures/compress/restructure.merge/issue-478-important.css create mode 100644 fixtures/compress/restructure.merge/issue-478-important.min.css create mode 100644 fixtures/compress/restructure.merge/issue-478-shared.css create mode 100644 fixtures/compress/restructure.merge/issue-478-shared.min.css create mode 100644 fixtures/compress/restructure.merge/issue-478-subset-before.css create mode 100644 fixtures/compress/restructure.merge/issue-478-subset-before.min.css create mode 100644 fixtures/compress/restructure.merge/issue-478-subset.css create mode 100644 fixtures/compress/restructure.merge/issue-478-subset.min.css create mode 100644 fixtures/compress/restructure.merge/issue-478.css create mode 100644 fixtures/compress/restructure.merge/issue-478.min.css diff --git a/fixtures/compress/restructure.merge/issue-417.css b/fixtures/compress/restructure.merge/issue-417.css new file mode 100644 index 00000000..61f53171 --- /dev/null +++ b/fixtures/compress/restructure.merge/issue-417.css @@ -0,0 +1,18 @@ +/* + Issue #417 + Same class of bug as #478: extracting `border-bottom` and then + applying `border` afterwards resets the bottom edge. +*/ + +.class1 { + border-bottom: 1px solid black; +} + +.class2 { + border: 0; + border-bottom: 1px solid black; +} + +.class3 { + border: 0; +} diff --git a/fixtures/compress/restructure.merge/issue-417.min.css b/fixtures/compress/restructure.merge/issue-417.min.css new file mode 100644 index 00000000..cefb4d05 --- /dev/null +++ b/fixtures/compress/restructure.merge/issue-417.min.css @@ -0,0 +1 @@ +.class1{border-bottom:1px solid #000}.class2{border:0;border-bottom:1px solid #000}.class3{border:0} diff --git a/fixtures/compress/restructure.merge/issue-478-all.css b/fixtures/compress/restructure.merge/issue-478-all.css new file mode 100644 index 00000000..36bb65b7 --- /dev/null +++ b/fixtures/compress/restructure.merge/issue-478-all.css @@ -0,0 +1,17 @@ +/* + Issue #478 + `all` resets almost every property. Extracting `color`/`display` into + a shared rule in front of a leftover `all` would wipe them. +*/ + +.a { + all: unset; + color: red; + display: block; +} + +.b { + all: initial; + color: red; + display: block; +} diff --git a/fixtures/compress/restructure.merge/issue-478-all.min.css b/fixtures/compress/restructure.merge/issue-478-all.min.css new file mode 100644 index 00000000..91f640b5 --- /dev/null +++ b/fixtures/compress/restructure.merge/issue-478-all.min.css @@ -0,0 +1 @@ +.a{all:unset;color:red;display:block}.b{all:initial;color:red;display:block} diff --git a/fixtures/compress/restructure.merge/issue-478-important.css b/fixtures/compress/restructure.merge/issue-478-important.css new file mode 100644 index 00000000..70a8e837 --- /dev/null +++ b/fixtures/compress/restructure.merge/issue-478-important.css @@ -0,0 +1,19 @@ +/* + Issue #478 + !important on the longhand makes order irrelevant, so it is still + safe to extract `background-size` in front of `background`. +*/ + +.a { + background: none; + background-size: 1 !important; + color: red; + display: block; +} + +.b { + background: some; + background-size: 1 !important; + color: red; + display: block; +} diff --git a/fixtures/compress/restructure.merge/issue-478-important.min.css b/fixtures/compress/restructure.merge/issue-478-important.min.css new file mode 100644 index 00000000..a998f2a0 --- /dev/null +++ b/fixtures/compress/restructure.merge/issue-478-important.min.css @@ -0,0 +1 @@ +.a,.b{background-size:1!important;color:red;display:block}.a{background:0 0}.b{background:some} diff --git a/fixtures/compress/restructure.merge/issue-478-shared.css b/fixtures/compress/restructure.merge/issue-478-shared.css new file mode 100644 index 00000000..c1885693 --- /dev/null +++ b/fixtures/compress/restructure.merge/issue-478-shared.css @@ -0,0 +1,19 @@ +/* + Issue #478 + Unrelated equal declarations can still be pulled out. The longhand + that a remaining shorthand would reset must stay after that shorthand. +*/ + +.a { + background: none; + background-size: cover; + color: red; + display: block; +} + +.b { + background: url(x); + background-size: cover; + color: red; + display: block; +} diff --git a/fixtures/compress/restructure.merge/issue-478-shared.min.css b/fixtures/compress/restructure.merge/issue-478-shared.min.css new file mode 100644 index 00000000..d074c67e --- /dev/null +++ b/fixtures/compress/restructure.merge/issue-478-shared.min.css @@ -0,0 +1 @@ +.a,.b{color:red;display:block}.a{background:0 0;background-size:cover}.b{background:url(x);background-size:cover} diff --git a/fixtures/compress/restructure.merge/issue-478-subset-before.css b/fixtures/compress/restructure.merge/issue-478-subset-before.css new file mode 100644 index 00000000..782b5069 --- /dev/null +++ b/fixtures/compress/restructure.merge/issue-478-subset-before.css @@ -0,0 +1,16 @@ +/* + Issue #478 + Subset merge where the extra shorthand stays in the earlier rule is + safe: `background` then `background-size` keeps the longhand. +*/ + +.a { + background: none; + background-size: 1; + b: cccccccc; +} + +.b { + background-size: 1; + b: cccccccc; +} diff --git a/fixtures/compress/restructure.merge/issue-478-subset-before.min.css b/fixtures/compress/restructure.merge/issue-478-subset-before.min.css new file mode 100644 index 00000000..abe86f1c --- /dev/null +++ b/fixtures/compress/restructure.merge/issue-478-subset-before.min.css @@ -0,0 +1 @@ +.a{background:0 0}.a,.b{background-size:1;b:cccccccc} \ No newline at end of file diff --git a/fixtures/compress/restructure.merge/issue-478-subset.css b/fixtures/compress/restructure.merge/issue-478-subset.css new file mode 100644 index 00000000..47d88048 --- /dev/null +++ b/fixtures/compress/restructure.merge/issue-478-subset.css @@ -0,0 +1,16 @@ +/* + Issue #478 + Subset merge: the later rule has an extra shorthand. Do not keep the + shared longhand in the merged rule before that shorthand. +*/ + +.a { + background-size: 1; + b: cccccccc; +} + +.b { + background: some; + background-size: 1; + b: cccccccc; +} diff --git a/fixtures/compress/restructure.merge/issue-478-subset.min.css b/fixtures/compress/restructure.merge/issue-478-subset.min.css new file mode 100644 index 00000000..1bc6aabf --- /dev/null +++ b/fixtures/compress/restructure.merge/issue-478-subset.min.css @@ -0,0 +1 @@ +.a,.b{b:cccccccc}.a{background-size:1}.b{background:some;background-size:1} diff --git a/fixtures/compress/restructure.merge/issue-478.css b/fixtures/compress/restructure.merge/issue-478.css new file mode 100644 index 00000000..5546e56f --- /dev/null +++ b/fixtures/compress/restructure.merge/issue-478.css @@ -0,0 +1,18 @@ +/* + Issue #478 + Do not extract a longhand into a shared rule that sits before a + later shorthand. `background` resets `background-size`, so leaving + `background-size` first wipes it in the browser. +*/ + +.a { + background: none; + background-size: 1; + b: c; +} + +.b { + background: some; + background-size: 1; + b: c; +} diff --git a/fixtures/compress/restructure.merge/issue-478.min.css b/fixtures/compress/restructure.merge/issue-478.min.css new file mode 100644 index 00000000..44f2d6c6 --- /dev/null +++ b/fixtures/compress/restructure.merge/issue-478.min.css @@ -0,0 +1 @@ +.a{background:0 0;background-size:1;b:c}.b{background:some;background-size:1;b:c} From 55ed5219f9f452f65d674ca94179377e5eb18b8d Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 20 Sep 2026 19:31:24 +0000 Subject: [PATCH 2/2] fix: do not extract longhands a leftover shorthand would reset When restructRuleset pulls equal declarations into a shared rule, a longhand such as background-size can end up before a leftover background shorthand and get reset in the browser. Refuse to extract a declaration when a remaining unique shorthand would sit after it (or when an extracted shorthand would sit after a remaining longhand) and reset its atoms. Unrelated equals still merge. Fixes #478. Co-authored-by: David --- lib/restructure/8-restructRuleset.js | 4 +- lib/restructure/propertyFamily.js | 194 +++++++++++++++++++++++++++ lib/restructure/utils.js | 76 ++++++++++- test/propertyFamily.js | 49 +++++++ 4 files changed, 318 insertions(+), 5 deletions(-) create mode 100644 lib/restructure/propertyFamily.js create mode 100644 test/propertyFamily.js diff --git a/lib/restructure/8-restructRuleset.js b/lib/restructure/8-restructRuleset.js index c9cbf094..94ed35e0 100644 --- a/lib/restructure/8-restructRuleset.js +++ b/lib/restructure/8-restructRuleset.js @@ -2,7 +2,7 @@ import { List, walk } from 'css-tree'; import { unsafeToSkipNode, isEqualSelectors, - compareDeclarations, + compareDeclarationsSafe, addSelectors } from './utils.js'; @@ -76,7 +76,7 @@ function processRule(node, item, list) { } // try to join by properties - const diff = compareDeclarations(block.children, prevBlock.children); + const diff = compareDeclarationsSafe(block.children, prevBlock.children, allowMergeUp); // console.log(diff.eq, diff.ne1, diff.ne2); diff --git a/lib/restructure/propertyFamily.js b/lib/restructure/propertyFamily.js new file mode 100644 index 00000000..28b87543 --- /dev/null +++ b/lib/restructure/propertyFamily.js @@ -0,0 +1,194 @@ +import { property as resolveProperty } from 'css-tree'; + +// Shorthand → the longhands it sets (and therefore resets when omitted). +// A leading "-" is treated as a suffix of the shorthand name, matching the +// historical property-family map on the unfinished `property-family` branch. +// Two properties overlap when their expanded atomic longhands intersect. +const SHORTHANDS = { + // CSS 2.1 + 'list-style': ['-type', '-position', '-image'], + 'margin': ['-top', '-right', '-bottom', '-left'], + 'padding': ['-top', '-right', '-bottom', '-left'], + 'outline': ['-width', '-style', '-color'], + 'border': ['-width', '-style', '-color', '-image'], + 'border-width': ['border-top-width', 'border-right-width', 'border-bottom-width', 'border-left-width'], + 'border-style': ['border-top-style', 'border-right-style', 'border-bottom-style', 'border-left-style'], + 'border-color': ['border-top-color', 'border-right-color', 'border-bottom-color', 'border-left-color'], + 'border-top': ['-width', '-style', '-color'], + 'border-right': ['-width', '-style', '-color'], + 'border-bottom': ['-width', '-style', '-color'], + 'border-left': ['-width', '-style', '-color'], + + // CSS Backgrounds and Borders + 'background': ['-image', '-position', '-size', '-repeat', '-origin', '-clip', '-attachment', '-color'], + 'background-position': ['-x', '-y'], + 'border-radius': ['border-top-left-radius', 'border-top-right-radius', 'border-bottom-right-radius', 'border-bottom-left-radius'], + 'border-image': ['-source', '-slice', '-width', '-outset', '-repeat'], + + // CSS Fonts + 'font': ['-style', '-variant', '-weight', '-stretch', '-size', 'line-height', '-family'], + 'font-variant': ['-ligatures', '-alternates', '-caps', '-numeric', '-east-asian'], + + // CSS Masking + 'mask': ['-image', '-mode', '-position', '-size', '-repeat', '-origin', '-clip', '-composite'], + 'mask-border': ['-source', '-slice', '-width', '-outset', '-repeat', '-mode'], + + // CSS Multi-column + 'columns': ['column-width', 'column-count'], + 'column-rule': ['-width', '-style', '-color'], + + // CSS Text Decoration + 'text-decoration': ['-line', '-style', '-color', '-thickness'], + 'text-emphasis': ['-style', '-color'], + + // CSS Animations / Transitions + 'animation': ['-name', '-duration', '-timing-function', '-delay', '-iteration-count', '-direction', '-fill-mode', '-play-state'], + 'transition': ['-property', '-duration', '-timing-function', '-delay'], + + // CSS Flexible Box / Grid + 'flex': ['-grow', '-shrink', '-basis'], + 'flex-flow': ['-direction', '-wrap'], + 'gap': ['row-gap', 'column-gap'], + 'grid-gap': ['grid-row-gap', 'grid-column-gap'], + 'place-content': ['align-content', 'justify-content'], + 'place-items': ['align-items', 'justify-items'], + 'place-self': ['align-self', 'justify-self'], + 'grid': ['grid-template-rows', 'grid-template-columns', 'grid-template-areas', 'grid-auto-rows', 'grid-auto-columns', 'grid-auto-flow'], + 'grid-template': ['-rows', '-columns', '-areas'], + 'grid-area': ['grid-row-start', 'grid-column-start', 'grid-row-end', 'grid-column-end'], + 'grid-row': ['-start', '-end'], + 'grid-column': ['-start', '-end'], + + // CSS Overflow / Position + 'overflow': ['-x', '-y'], + 'inset': ['top', 'right', 'bottom', 'left'], + + // CSS Logical + 'margin-block': ['-start', '-end'], + 'margin-inline': ['-start', '-end'], + 'padding-block': ['-start', '-end'], + 'padding-inline': ['-start', '-end'], + 'inset-block': ['-start', '-end'], + 'inset-inline': ['-start', '-end'], + 'border-block': ['-width', '-style', '-color'], + 'border-inline': ['-width', '-style', '-color'], + 'border-block-width': ['border-block-start-width', 'border-block-end-width'], + 'border-block-style': ['border-block-start-style', 'border-block-end-style'], + 'border-block-color': ['border-block-start-color', 'border-block-end-color'], + 'border-inline-width': ['border-inline-start-width', 'border-inline-end-width'], + 'border-inline-style': ['border-inline-start-style', 'border-inline-end-style'], + 'border-inline-color': ['border-inline-start-color', 'border-inline-end-color'], + 'border-block-start': ['-width', '-style', '-color'], + 'border-block-end': ['-width', '-style', '-color'], + 'border-inline-start': ['-width', '-style', '-color'], + 'border-inline-end': ['-width', '-style', '-color'] +}; + +// `all` resets every property except these (CSS Cascading). +const ALL_EXEMPT = new Set(['unicode-bidi', 'direction']); + +const atomCache = new Map(); + +function resolveChild(parent, child) { + // "-size" → "background-size"; leave full names and custom props alone + if (child.charCodeAt(0) === 45 && child.charCodeAt(1) !== 45) { + return parent + child; + } + + return child; +} + +function computeAtoms(basename, seen) { + if (seen.has(basename)) { + return []; + } + + seen.add(basename); + + const children = SHORTHANDS[basename]; + + if (!children) { + return [basename]; + } + + const atoms = []; + + for (const child of children) { + atoms.push(...computeAtoms(resolveChild(basename, child), seen)); + } + + return atoms; +} + +function getAtoms(basename) { + let atoms = atomCache.get(basename); + + if (!atoms) { + atoms = computeAtoms(basename, new Set()); + atomCache.set(basename, atoms); + } + + return atoms; +} + +function atomsIntersect(a, b) { + if (a === b) { + return true; + } + + const atomsA = getAtoms(a); + const atomsB = getAtoms(b); + const setA = atomsA.length >= atomsB.length ? new Set(atomsA) : new Set(atomsB); + const other = atomsA.length >= atomsB.length ? atomsB : atomsA; + + return other.some((atom) => setA.has(atom)); +} + +export function propertiesOverlap(propertyA, propertyB) { + const a = resolveProperty(propertyA); + const b = resolveProperty(propertyB); + + if (a.custom || b.custom) { + return a.custom && b.custom && a.name === b.name; + } + + if (a.basename === 'all' || b.basename === 'all') { + const other = a.basename === 'all' ? b.basename : a.basename; + + return other === 'all' || !ALL_EXEMPT.has(other); + } + + return atomsIntersect(a.basename, b.basename); +} + +// True when `shorthandDecl` sets every atom of `longhandDecl`, so placing +// the shorthand later would reset the longhand. Different !important flags +// make cascade order irrelevant, so those pairs are not treated as unsafe. +export function isResettingShorthand(shorthandDecl, longhandDecl) { + if (Boolean(shorthandDecl.important) !== Boolean(longhandDecl.important)) { + return false; + } + + const shorthand = resolveProperty(shorthandDecl.property); + const longhand = resolveProperty(longhandDecl.property); + + if (shorthand.custom || longhand.custom) { + return false; + } + + if (shorthand.basename === 'all') { + return longhand.basename !== 'all' && !ALL_EXEMPT.has(longhand.basename); + } + + if (shorthand.basename === longhand.basename) { + return false; + } + + if (!atomsIntersect(shorthand.basename, longhand.basename)) { + return false; + } + + const shorthandAtoms = new Set(getAtoms(shorthand.basename)); + + return getAtoms(longhand.basename).every((atom) => shorthandAtoms.has(atom)); +} diff --git a/lib/restructure/utils.js b/lib/restructure/utils.js index 792cc59b..258b37d6 100644 --- a/lib/restructure/utils.js +++ b/lib/restructure/utils.js @@ -1,3 +1,5 @@ +import { isResettingShorthand } from './propertyFamily.js'; + const { hasOwnProperty } = Object.prototype; export function isEqualSelectors(a, b) { @@ -24,7 +26,7 @@ export function isEqualDeclarations(a, b) { return cursor1 === null && cursor2 === null; } -export function compareDeclarations(declarations1, declarations2) { +export function compareDeclarations(declarations1, declarations2, canExtract) { const result = { eq: [], ne1: [], @@ -34,6 +36,7 @@ export function compareDeclarations(declarations1, declarations2) { const fingerprints = Object.create(null); const declarations2hash = Object.create(null); + const extractable = typeof canExtract === 'function' ? canExtract : null; for (let cursor = declarations2.head; cursor; cursor = cursor.next) { declarations2hash[cursor.data.id] = true; @@ -41,12 +44,16 @@ export function compareDeclarations(declarations1, declarations2) { for (let cursor = declarations1.head; cursor; cursor = cursor.next) { const data = cursor.data; + const allowExtract = extractable === null || extractable(data); - if (data.fingerprint) { + // Skip fingerprints for declarations we refuse to extract so the + // matching copy in declarations2 stays a real unique (ne2), not an + // "overridden" leftover. + if (data.fingerprint && allowExtract) { fingerprints[data.fingerprint] = data.important; } - if (declarations2hash[data.id]) { + if (declarations2hash[data.id] && allowExtract) { declarations2hash[data.id] = false; result.eq.push(data); } else { @@ -72,6 +79,69 @@ export function compareDeclarations(declarations1, declarations2) { return result; } +function someReset(shorthands, longhand) { + return shorthands.some((shorthand) => isResettingShorthand(shorthand, longhand)); +} + +// After compareDeclarations, decide which equal declarations would be unsafe +// to pull into a shared rule given where the leftover uniques will land. +// +// extract-equal, merge up: shared rule is inserted first → all uniques after +// extract-equal, no merge up: shared rule sits between prev and current +// prev is subset: eq stays in prev, ne1 stays in current (after) +// current is subset: ne2 stays in prev (before), eq stays in current +function unsafeToExtract(decl, diff, allowMergeUp) { + let uniqueBefore = []; + let uniqueAfter = []; + + if (diff.ne1.length && diff.ne2.length) { + if (allowMergeUp) { + uniqueAfter = diff.ne1.concat(diff.ne2overrided); + } else { + uniqueBefore = diff.ne2overrided; + uniqueAfter = diff.ne1; + } + } else if (diff.ne1.length) { + uniqueAfter = diff.ne1; + } else if (diff.ne2.length) { + uniqueBefore = diff.ne2; + } + + // leftover shorthand after this extracted longhand → longhand is wiped + if (someReset(uniqueAfter, decl)) { + return true; + } + + // this extracted shorthand after a leftover longhand → longhand is wiped + return uniqueBefore.some((unique) => isResettingShorthand(decl, unique)); +} + +// Keep longhands from being extracted when a leftover shorthand (or `all`) +// would sit on the other side of them and reset the shared atoms. +export function compareDeclarationsSafe(declarations1, declarations2, allowMergeUp) { + const blockedIds = new Set(); + + for (;;) { + const diff = compareDeclarations( + declarations1, + declarations2, + (data) => !blockedIds.has(data.id) + ); + let added = false; + + for (const decl of diff.eq) { + if (!blockedIds.has(decl.id) && unsafeToExtract(decl, diff, allowMergeUp)) { + blockedIds.add(decl.id); + added = true; + } + } + + if (!added) { + return diff; + } + } +} + export function addSelectors(dest, source) { source.forEach((sourceData) => { const newStr = sourceData.id; diff --git a/test/propertyFamily.js b/test/propertyFamily.js new file mode 100644 index 00000000..058f54ab --- /dev/null +++ b/test/propertyFamily.js @@ -0,0 +1,49 @@ +import { strictEqual } from 'assert'; +import { propertiesOverlap, isResettingShorthand } from '../lib/restructure/propertyFamily.js'; + +function decl(property, important) { + return { property, important: Boolean(important) }; +} + +describe('propertyFamily', () => { + describe('propertiesOverlap()', () => { + it('matches a shorthand with the longhands it resets', () => { + strictEqual(propertiesOverlap('background', 'background-size'), true); + strictEqual(propertiesOverlap('background-size', 'background'), true); + strictEqual(propertiesOverlap('border', 'border-bottom'), true); + strictEqual(propertiesOverlap('font', 'line-height'), true); + strictEqual(propertiesOverlap('transition', 'transition-property'), true); + }); + + it('does not match unrelated or sibling longhands', () => { + strictEqual(propertiesOverlap('background-size', 'color'), false); + strictEqual(propertiesOverlap('overflow', 'overflow-wrap'), false); + strictEqual(propertiesOverlap('flex', 'flex-direction'), false); + strictEqual(propertiesOverlap('border', 'border-radius'), false); + }); + + it('treats all as overlapping everything except unicode-bidi/direction', () => { + strictEqual(propertiesOverlap('all', 'color'), true); + strictEqual(propertiesOverlap('all', 'background-size'), true); + strictEqual(propertiesOverlap('all', 'direction'), false); + strictEqual(propertiesOverlap('all', 'unicode-bidi'), false); + strictEqual(propertiesOverlap('all', '--foo'), false); + }); + }); + + describe('isResettingShorthand()', () => { + it('detects when a shorthand would reset a longhand', () => { + strictEqual(isResettingShorthand(decl('background'), decl('background-size')), true); + strictEqual(isResettingShorthand(decl('background-size'), decl('background')), false); + strictEqual(isResettingShorthand(decl('border'), decl('border-bottom')), true); + strictEqual(isResettingShorthand(decl('border-bottom'), decl('border')), false); + strictEqual(isResettingShorthand(decl('all'), decl('color')), true); + strictEqual(isResettingShorthand(decl('color'), decl('all')), false); + }); + + it('ignores pairs whose !important flags differ', () => { + strictEqual(isResettingShorthand(decl('background'), decl('background-size', true)), false); + strictEqual(isResettingShorthand(decl('background', true), decl('background-size')), false); + }); + }); +});