using getComputedStyle with jsdom
Asked Answered
R

2

7

I fail to get the computed style value of color with jsdom:

require("jsdom").env({
    html: '<html><head><style> html { color: red; } </style></head><body></body></html>',
    done: function(errors, window) {

        console.log('color: "'+window.getComputedStyle(window.document.body).color+'"');
    }
});

The previous test returns "" instead of "rgb(255, 0, 0)" or "red" ...
(note that this work properly in a browser)

Do I miss something ?

Rostand answered 24/4, 2015 at 13:40 Comment(2)
wrapping console.log ... with setTimeout ?Morven
nothing, but maybe there is a trick to trigger the css computation ...Rostand
I
8

It seems like jsdom does not implement inheritance for getComputedStyle.

If you get the computed style of the html tag it should work however.

Impropriety answered 25/4, 2015 at 11:48 Comment(0)
D
0

I also ran into some issues with getComputedStyle with jsdom in Typescript. I was trying to be able to test rendered CSS better. I decided to instead write a small helper function to help me easily get CSS styles from DebugElement / DebugElement[] / string / string[]. I hope this is useful to someone!

My Helper Function:

//IMPORTS REMOVED FOR BREVITY

/**
 * Checks if an unknown object is a string using typeof
 * @param obj an object of unknown type that is being type-checked
 * @returns true if the object is a string, false otherwise
 */
function isString(obj: unknown): boolean {
    return typeof(obj) === 'string';
}

/**
 * Helper function to help get a list of all rendered CSS styles from a CSS query or Debug Element.
 *
 * Overloads:
 *  getStyleByCSS(debugElement: DebugElement)
 *      Gets the styles from a debug element and returns the style declarations.
 *
 *  getStyleByCSS(debugElement: DebugElement[])
 *      Gets the styles for each debug element in an array and returns an array of objects containing each debug element and it's respective styles.
 *
 *  getStyleByCSS(debugElement: string | string[], fixture: ComponentFixture<unknown>)
 *      Queries a given fixture for ALL debug elements for a given CSS query/queries and returns an array of objects containing each debug element
 *          and it's respective styles.
 *
 */
export function getStyleByCSS(queryEl: DebugElement): CSSStyleDeclaration;
export function getStyleByCSS(queryEl: DebugElement[]): ElementWithStyles[];
export function getStyleByCSS(queryEl: string | string[], fixture: ComponentFixture<unknown>): ElementWithStyles[];
export function getStyleByCSS(queryEl: unknown, fixture?: ComponentFixture<unknown>): CSSStyleDeclaration | ElementWithStyles[] {
    // If we are using the Overload that just takes in a DebugElement
    if(!isString(queryEl) && !Array.isArray(queryEl)) {
        let stylesToReturn: CSSStyleDeclaration = ((queryEl as DebugElement).styles._values) as unknown as CSSStyleDeclaration;
        return stylesToReturn as CSSStyleDeclaration;

    // If we are using the Overload that just takes in a string and a fixture object
    } else if (isString(queryEl) && !Array.isArray(queryEl)) {
        let returnElements: ElementWithStyles[] = [];
        let debugElements = fixture.debugElement.queryAll(By.css(queryEl as string));
        // Check each debug element queried by the given string and format an array of objects containing each debug element and it's respective styles.
        debugElements.forEach(el => {
            let thisElWithStyles: ElementWithStyles = {
                debugElement: el,
                styles: ((el as DebugElement).styles._values) as unknown as CSSStyleDeclaration
            };
            returnElements.push(thisElWithStyles);
        });

        return returnElements as ElementWithStyles[];
    } else if(Array.isArray(queryEl)) {
        // Can't get the styles of an empty array of queries / debug elements, so this situation is invalid.
        if(queryEl.length === 0) {
            throw new Error('Cannot get CSS declarations of an array of length 0');
        } else {
            let returnElements: ElementWithStyles[] = [];
            // If we are using the Overload that just takes in a DebugElement[]
            if(!isString(queryEl[0])) {
                // Check each debug element and format an array of objects containing each debug element and it's respective styles.
                queryEl.forEach(el => {
                    let thisElWithStyles: ElementWithStyles = {
                        debugElement: el,
                        styles: ((el as DebugElement).styles._values) as unknown as CSSStyleDeclaration
                    };
                    returnElements.push(thisElWithStyles);
                });

                return returnElements as ElementWithStyles[];

            // If we are using the Overload that just takes in a string[]
            } else if(isString(queryEl[0])) {
                let returnEls: ElementWithStyles[] = [];
                let debugElements: DebugElement[] = [];

                // Get all debug elements for each string in the array of CSS queries
                queryEl.forEach(el => {
                    let thisEl: DebugElement[] = fixture.debugElement.queryAll(By.css(el as string));
                    thisEl.forEach(y => {
                        debugElements.push(y);
                    });
                });

                // Check each debug element queried by the given strings and format an array of objects containing each debug element and it's respective styles.
                debugElements.forEach(el => {
                    let thisElWithStyles: ElementWithStyles = {
                        debugElement: el,
                        styles: ((el as DebugElement).styles._values) as unknown as CSSStyleDeclaration
                    };
                    returnEls.push(thisElWithStyles);
                });

                return returnEls as ElementWithStyles[];
            } else {
                // If we reach this point, an invalid type was passed into the array
                throw new Error('Invalid array type. Must be of type DebugElement or String');
            }
        }
    } else {
        // If we reach this point, an invalid type was passed into the function
        throw new Error('Invalid type. Please use a DebugElement or string');
    }
}

/**
 * Interface used to defined the variables that are being returned by various queries, contains a debug element and it's respective styles.
 */
export interface ElementWithStyles {
    debugElement: DebugElement;
    styles: CSSStyleDeclaration;
}

My Usage:


// Majority of code removed for Brevity

let queriedDebugElements = fixture.debugElement.queryAll(By.css('.my-query-class'));

// Get the rendered CSS styling for each queried object
let elementStyles: ElementWithStyles[] = getStyleByCSS(shownChunks);
// Various usages
// Find all queried objects with a given background color
let elementByBackground = elementStyles.map(x => x.styles).filter(x => x.background === 'rgb(256, 256, 256)');
// Find all queried objects with a width that matches the width of the first element
let elementWithSameWidthAsFirst = elementStyles.map(x => x.styles).filter(x => x.width === elementStyles[0].styles.width); 
Drops answered 7/5 at 18:23 Comment(3)
This may have helped but it's hard to tell because the imports are removed. VS Code doesn't know what DebugElement is and neither do I. This post suggests it's part of Angular 2 which I am not using.Simar
DebugElement is a common component returned from componentFixture and is used to query the DOM: testing-angular.com/testing-components/…Drops
That's good to know but my point is the OP is not necessarily using Angular, and even if they are, they didn't say they were looking for an angular-only answer. Your answer is good and I'm not suggesting you should remove it, but it's worth saying it's an Angular-only solution.Simar

© 2022 - 2024 — McMap. All rights reserved.