Skip to content

Commit

Permalink
#921@trivial: Fixes problem with percentage values in media queries.
Browse files Browse the repository at this point in the history
  • Loading branch information
capricorn86 committed May 18, 2023
1 parent 05060aa commit 0e50f0f
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 67 deletions.
Expand Up @@ -209,7 +209,7 @@ export default class CSSStyleDeclarationElementStyle {
value: fontSize.value,
rootFontSize,
parentFontSize,
parentWidth: parentFontSize
parentSize: parentFontSize
});
if ((<IElement>parentElement.element).tagName === 'HTML') {
rootFontSize = parsedValue;
Expand All @@ -230,10 +230,9 @@ export default class CSSStyleDeclarationElementStyle {
value: property.value,
rootFontSize,
parentFontSize,
parentWidth:
name === 'font-size'
? parentFontSize
: this.element.ownerDocument.defaultView.innerWidth

// TODO: Only "font-size" is supported when using percentage values. Add support for other properties.
parentSize: name === 'font-size' ? parentFontSize : 0
});
}
}
Expand Down Expand Up @@ -329,14 +328,14 @@ export default class CSSStyleDeclarationElementStyle {
* @param options.value Value.
* @param options.rootFontSize Root font size.
* @param options.parentFontSize Parent font size.
* @param [options.parentWidth] Parent width.
* @param [options.parentSize] Parent width.
* @returns CSS value.
*/
private parseMeasurementsInValue(options: {
value: string;
rootFontSize: string | number;
parentFontSize: string | number;
parentWidth: string | number;
parentSize: string | number;
}): string {
const regexp = new RegExp(CSS_MEASUREMENT_REGEXP);
let newValue = options.value;
Expand All @@ -349,7 +348,7 @@ export default class CSSStyleDeclarationElementStyle {
value: match[0],
rootFontSize: options.rootFontSize,
parentFontSize: options.parentFontSize,
parentWidth: options.parentWidth
parentSize: options.parentSize
});

if (valueInPixels !== null) {
Expand Down
Expand Up @@ -12,15 +12,15 @@ export default class CSSMeasurementConverter {
* @param options.value Measurement (e.g. "10px", "10rem" or "10em").
* @param options.rootFontSize Root font size in pixels.
* @param options.parentFontSize Parent font size in pixels.
* @param options.parentWidth
* @param [options.parentSize] Parent size in pixels.
* @returns Measurement in pixels.
*/
public static toPixels(options: {
ownerWindow: IWindow;
value: string;
rootFontSize: string | number;
parentFontSize: string | number;
parentWidth: string | number;
parentSize?: string | number | null;
}): number | null {
const value = parseFloat(options.value);
const unit = options.value.replace(value.toString(), '');
Expand All @@ -41,7 +41,9 @@ export default class CSSMeasurementConverter {
case 'vh':
return this.round((value * options.ownerWindow.innerHeight) / 100);
case '%':
return this.round((value * parseFloat(<string>options.parentWidth)) / 100);
return options.parentSize !== undefined && options.parentSize !== null
? this.round((value * parseFloat(<string>options.parentSize)) / 100)
: null;
case 'vmin':
return this.round(
(value * Math.min(options.ownerWindow.innerWidth, options.ownerWindow.innerHeight)) / 100
Expand Down
116 changes: 60 additions & 56 deletions packages/happy-dom/src/match-media/MediaQueryItem.ts
Expand Up @@ -124,62 +124,68 @@ export default class MediaQueryItem {
* @returns "true" if the range matches.
*/
private matchesRange(range: IMediaQueryRange): boolean {
const size =
const windowSize =
range.type === 'width' ? this.ownerWindow.innerWidth : this.ownerWindow.innerHeight;

if (range.before) {
const beforeValue = this.toPixels(range.before.value);
if (!isNaN(beforeValue)) {
switch (range.before.operator) {
case '<':
if (beforeValue >= size) {
return false;
}
break;
case '<=':
if (beforeValue > size) {
return false;
}
break;
case '>':
if (beforeValue <= size) {
return false;
}
break;
case '>=':
if (beforeValue < size) {
return false;
}
break;
}

if (beforeValue === null) {
return false;
}

switch (range.before.operator) {
case '<':
if (beforeValue >= windowSize) {
return false;
}
break;
case '<=':
if (beforeValue > windowSize) {
return false;
}
break;
case '>':
if (beforeValue <= windowSize) {
return false;
}
break;
case '>=':
if (beforeValue < windowSize) {
return false;
}
break;
}
}

if (range.after) {
const afterValue = this.toPixels(range.after.value);
if (!isNaN(afterValue)) {
switch (range.after.operator) {
case '<':
if (size >= afterValue) {
return false;
}
break;
case '<=':
if (size > afterValue) {
return false;
}
break;
case '>':
if (size <= afterValue) {
return false;
}
break;
case '>=':
if (size < afterValue) {
return false;
}
break;
}

if (afterValue === null) {
return false;
}

switch (range.after.operator) {
case '<':
if (windowSize >= afterValue) {
return false;
}
break;
case '<=':
if (windowSize > afterValue) {
return false;
}
break;
case '>':
if (windowSize <= afterValue) {
return false;
}
break;
case '>=':
if (windowSize < afterValue) {
return false;
}
break;
}
}

Expand Down Expand Up @@ -217,16 +223,16 @@ export default class MediaQueryItem {
switch (rule.name) {
case 'min-width':
const minWidth = this.toPixels(rule.value);
return !isNaN(minWidth) && this.ownerWindow.innerWidth >= minWidth;
return minWidth !== null && this.ownerWindow.innerWidth >= minWidth;
case 'max-width':
const maxWidth = this.toPixels(rule.value);
return !isNaN(maxWidth) && this.ownerWindow.innerWidth <= maxWidth;
return maxWidth !== null && this.ownerWindow.innerWidth <= maxWidth;
case 'min-height':
const minHeight = this.toPixels(rule.value);
return !isNaN(minHeight) && this.ownerWindow.innerHeight >= minHeight;
return minHeight !== null && this.ownerWindow.innerHeight >= minHeight;
case 'max-height':
const maxHeight = this.toPixels(rule.value);
return !isNaN(maxHeight) && this.ownerWindow.innerHeight <= maxHeight;
return maxHeight !== null && this.ownerWindow.innerHeight <= maxHeight;
case 'orientation':
return rule.value === 'landscape'
? this.ownerWindow.innerWidth > this.ownerWindow.innerHeight
Expand Down Expand Up @@ -289,7 +295,7 @@ export default class MediaQueryItem {
* @param value Value.
* @returns Value in pixels.
*/
private toPixels(value: string): number {
private toPixels(value: string): number | null {
if (value.endsWith('em')) {
this.rootFontSize =
this.rootFontSize ||
Expand All @@ -300,16 +306,14 @@ export default class MediaQueryItem {
ownerWindow: this.ownerWindow,
value,
rootFontSize: this.rootFontSize,
parentFontSize: this.rootFontSize,
parentWidth: this.ownerWindow.innerWidth
parentFontSize: this.rootFontSize
});
}
return CSSMeasurementConverter.toPixels({
ownerWindow: this.ownerWindow,
value,
rootFontSize: 16,
parentFontSize: 16,
parentWidth: this.ownerWindow.innerWidth
parentFontSize: 16
});
}
}
10 changes: 10 additions & 0 deletions packages/happy-dom/test/match-media/MediaQueryList.test.ts
Expand Up @@ -130,6 +130,11 @@ describe('MediaQueryList', () => {
true
);

// Percentages should never match
expect(new MediaQueryList({ ownerWindow: window, media: '(min-width: 0%)' }).matches).toBe(
false
);

window.document.documentElement.style.fontSize = '10px';

expect(
Expand Down Expand Up @@ -180,6 +185,11 @@ describe('MediaQueryList', () => {
new MediaQueryList({ ownerWindow: window, media: '(min-height: 100vh)' }).matches
).toBe(true);

// Percentages should never match
expect(new MediaQueryList({ ownerWindow: window, media: '(min-height: 0%)' }).matches).toBe(
false
);

expect(
new MediaQueryList({ ownerWindow: window, media: `(min-height: ${769 / 16}rem)` }).matches
).toBe(false);
Expand Down
36 changes: 36 additions & 0 deletions packages/happy-dom/test/window/Window.test.ts
Expand Up @@ -436,6 +436,42 @@ describe('Window', () => {
expect(computedStyle.height).toBe('150px');
});

it('Returns a CSSStyleDeclaration object with computed styles containing "%" measurement values converted to pixels.', () => {
const parent = <IHTMLElement>document.createElement('div');
const element = <IHTMLElement>document.createElement('span');
const computedStyle = window.getComputedStyle(element);
const parentStyle = document.createElement('style');
const elementStyle = document.createElement('style');

window.happyDOM.setInnerWidth(1024);

parentStyle.innerHTML = `
html {
font-size: 62.5%;
}
div {
font-size: 1.5rem;
}
`;

elementStyle.innerHTML = `
span {
width: 100%;
height: 10em;
}
`;

parent.appendChild(elementStyle);
parent.appendChild(element);

document.body.appendChild(parentStyle);
document.body.appendChild(parent);

expect(computedStyle.width).toBe('0px');
expect(computedStyle.height).toBe('150px');
});

for (const measurement of [
{ value: '100vw', result: '1024px' },
{ value: '100vh', result: '768px' },
Expand Down

0 comments on commit 0e50f0f

Please sign in to comment.