Skip to content

feat(dom): DOM - toHaveStyle #154

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/dom/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"test": "NODE_ENV=test mocha"
},
"dependencies": {
"@adobe/css-tools": "^4.4.3",
"fast-deep-equal": "^3.1.3",
"tslib": "^2.6.2"
},
Expand Down
113 changes: 113 additions & 0 deletions packages/dom/src/lib/ElementAssertion.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Assertion, AssertionError } from "@assertive-ts/core";
import {parse} from '@adobe/css-tools'

export class ElementAssertion<T extends Element> extends Assertion<T> {

Expand Down Expand Up @@ -146,6 +147,118 @@ export class ElementAssertion<T extends Element> extends Assertion<T> {
return this.actual.className.split(/\s+/).filter(Boolean);
}

public toHaveStyle(css: Object|string): this {
if (
this.actual instanceof HTMLElement ||
this.actual['ownerDocument']
) {

const parsedCSS = typeof css === 'object'
? css
: parse(`selector { ${css} }`, {silent: true}).stylesheet

const window = this.actual.ownerDocument.defaultView;

const computedStyle = window?.getComputedStyle;

const expected = parsedCSS
console.log("expected: ", expected);
const received = computedStyle?.(this.actual);

interface StyleDeclaration {
property: string;
value: string;
}

let expectedStyle = {}
let receivedStyle = {}
let props: string[] = []
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's try to refactor these above so that we handle mutability.
Use const instead of let


const normalizer = document.createElement("div");
document.body.appendChild(normalizer);

if (typeof css === 'object') {
Object.entries(css).map(([property, value]) => {
props = [...props, property];

normalizer.style[property] = value;
const normalizedValue = window?.getComputedStyle(normalizer).getPropertyValue(property);

expectedStyle = {
...expectedStyle,
[property]: normalizedValue?.trim(),

};
Comment on lines +181 to +191
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Get this logic in a separate function in our helpers file. This help avoid dupllicated code below.


});
console.log("EXPECTED STYLE: ", expectedStyle);
} else {
const expectedRule = expected.rules[0];
expectedRule.declarations.map((declaration: StyleDeclaration) => {
const property = declaration.property;
const value = declaration.value;

props = [...props, property];

normalizer.style[property] = value;
const normalizedValue = window.getComputedStyle(normalizer).getPropertyValue(property);

expectedStyle = {
...expectedStyle,
[property]: normalizedValue.trim(),
};

return expectedStyle;
});
}
Comment on lines +203 to +213
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove duplicated code by moving it to a function.


document.body.removeChild(normalizer);


console.log("expected style: ",expectedStyle);

props.map((prop: string) => {
receivedStyle = {
...receivedStyle,
[prop]: received?.getPropertyValue(prop).trim(),
};
})

console.log("received style: ", receivedStyle);

const isSameStyle = !!Object.keys(expectedStyle).length &&
Object.entries(expectedStyle).every(([expectedProp, expectedValue]) => {
const isCustomProperty = expectedProp.startsWith('--')
const spellingVariants = [expectedProp]
expectedProp !== null;

if (!isCustomProperty) spellingVariants.push(expectedProp.toLowerCase())
return spellingVariants.some( searchProp =>
receivedStyle[searchProp] === expectedValue
)
})

Comment on lines +229 to +240
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's extract this in a new function to keep our code readable and simple.

console.log("isSameStyle: ", isSameStyle)
const error = new AssertionError({
actual: this.actual,
message: `Expected the element to have ${JSON.stringify(expectedStyle)} style`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For a better messaging here, we can add the element in the string as well.
For example:

Expected

element to have ${JSON.stringify(expectedStyle)} style

expected: expectedStyle
})
const invertedError = new AssertionError({
actual: this.actual,
message: `Expected the element to NOT have ${JSON.stringify(expectedStyle)} style`,
})

return this.execute({
assertWhen: isSameStyle,
error,
invertedError
});
}
return this;
}


/**
* Helper method to assert the presence or absence of class names.
*
Expand Down
42 changes: 40 additions & 2 deletions packages/dom/test/unit/lib/ElementAssertion.test.tsx
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For tests, keep in mind we want to test the the error assertion and its message. Also the inverted error and its message. depending on the test add both assertions

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AssertionError, expect } from "@assertive-ts/core";
import { render } from "@testing-library/react";
import { getByTestId, render } from "@testing-library/react";

import { ElementAssertion } from "../../../src/lib/ElementAssertion";

Expand Down Expand Up @@ -265,4 +265,42 @@ describe("[Unit] ElementAssertion.test.ts", () => {
});
});

});
describe(".toHaveStyle", () => {
context("when the element has the expected style when passed as string", () => {
it("returns the assertion instance when the styles are the same", () => {
const { getByTestId } = render(<div className="foo bar test" style={{ display: "flex", color: "red", border: "1px solid black" }} data-testid="test-div" />);
const divTest = getByTestId("test-div");
const test = new ElementAssertion(divTest);

expect(test.toHaveStyle("display: flex; color: red; border: 1px solid black")).toBeEqual(test);

});
it("fails the assertion when the styles are not the same", () => {
const { getByTestId } = render(<div className="foo bar test" style={{ display: "flex", color: "red" }} data-testid="test-div" />);
const divTest = getByTestId("test-div");
const test = new ElementAssertion(divTest);

expect(test.toHaveStyle("color: red; display: flex; border: 1px solid black;")).toBeEqual(test);

});
context("when the element has the expected style when passed as object", () => {
it("returns the assertion instance when the styles are the same", () => {
const { getByTestId } = render(<div className="foo bar test" style={{ display: "flex", color: "red", border: "1px solid black" }} data-testid="test-div" />);
const divTest = getByTestId("test-div");
const test = new ElementAssertion(divTest);

expect(test.toHaveStyle({color: "red", display: "flex", border: "1px solid black"})).toBeEqual(test);

});
it("fails the assertion when the styles are not the same", () => {
const { getByTestId } = render(<div className="foo bar test" style={{ display: "flex", color: "red" }} data-testid="test-div" />);
const divTest = getByTestId("test-div");
const test = new ElementAssertion(divTest);

expect(test.toHaveStyle({color: "red", display: "flex", border: "1px solid black"})).toBeEqual(test);

});
});
})
})
})
8 changes: 8 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ __metadata:
languageName: node
linkType: hard

"@adobe/css-tools@npm:^4.4.3":
version: 4.4.3
resolution: "@adobe/css-tools@npm:4.4.3"
checksum: 10/701379c514b7a43ca6681705a93cd57ad79565cfef9591122e9499897550cf324a5e5bb1bc51df0e7433cf0e91b962c90f18ac459dcc98b2431daa04aa63cb20
languageName: node
linkType: hard

"@ampproject/remapping@npm:^2.2.0":
version: 2.2.1
resolution: "@ampproject/remapping@npm:2.2.1"
Expand Down Expand Up @@ -49,6 +56,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@assertive-ts/dom@workspace:packages/dom"
dependencies:
"@adobe/css-tools": "npm:^4.4.3"
"@assertive-ts/core": "workspace:^"
"@testing-library/dom": "npm:^10.1.0"
"@testing-library/react": "npm:^16.0.0"
Expand Down
Loading