Skip to content

Commit 33320b6

Browse files
committed
Add: simplified logic and refactored functions
1 parent 2481a74 commit 33320b6

File tree

2 files changed

+51
-104
lines changed

2 files changed

+51
-104
lines changed

packages/dom/src/lib/ElementAssertion.ts

Lines changed: 38 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Assertion, AssertionError } from "@assertive-ts/core";
2-
import { parse } from "@adobe/css-tools";
3-
import { CssAtRuleAST, getProps, isSameStyle, normalizeStylesObject, normalizeStylesString } from "./helpers/helpers";
2+
import equal from "fast-deep-equal";
3+
4+
import { getReceivedStyle, normalizeStyles } from "./helpers/helpers";
45

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

@@ -144,59 +145,45 @@ export class ElementAssertion<T extends Element> extends Assertion<T> {
144145
);
145146
}
146147

147-
private getClassList(): string[] {
148-
return this.actual.className.split(/\s+/).filter(Boolean);
149-
}
150-
151148
/**
152149
* Asserts that the element has the specified CSS styles.
153150
*
154-
* @param css - The expected CSS styles.
151+
* @param expected - The expected CSS styles.
155152
* @returns The assertion instance.
156153
*/
157154

158-
public toHaveStyle(css: Object | string): this {
159-
if (this.actual instanceof HTMLElement || this.actual["ownerDocument"]) {
160-
const parsedCSS =
161-
typeof css === "object"
162-
? css
163-
: parse(`selector { ${css} }`, { silent: true }).stylesheet;
164-
165-
const window = this.actual.ownerDocument.defaultView;
166-
const computedStyle = window?.getComputedStyle;
167-
168-
const expected = parsedCSS as CssAtRuleAST;
169-
const received = computedStyle?.(this.actual) as CSSStyleDeclaration;
170-
171-
172-
const { props, expectedStyle } =
173-
typeof css === "object"
174-
? normalizeStylesObject(css, window!)
175-
: normalizeStylesString(expected, window!);
176-
177-
const receivedStyle = getProps(props, received);
178-
179-
const error = new AssertionError({
180-
actual: this.actual,
181-
message: `Expected the element to have ${JSON.stringify(expectedStyle
182-
)} style`,
183-
expected: expectedStyle,
184-
});
185-
const invertedError = new AssertionError({
186-
actual: this.actual,
187-
message: `Expected the element to NOT have ${JSON.stringify(
188-
expectedStyle
189-
)} style`,
190-
});
191-
return this.execute({
192-
assertWhen: isSameStyle(expectedStyle, receivedStyle),
193-
error,
194-
invertedError,
195-
});
155+
public toHaveStyle(expected: Partial<CSSStyleDeclaration>): this {
156+
if (!this.actual.ownerDocument.defaultView) {
157+
throw new Error("The element is not attached to a document with a default view.");
158+
}
159+
if (!(this.actual instanceof HTMLElement)) {
160+
throw new Error("The element is not an HTMLElement.");
196161
}
197-
return this;
198-
}
199162

163+
const window = this.actual.ownerDocument.defaultView;
164+
165+
const received = window.getComputedStyle(this.actual);
166+
167+
const { props, expectedStyle } = normalizeStyles(expected);
168+
169+
const receivedStyle = getReceivedStyle(props, received);
170+
171+
const error = new AssertionError({
172+
actual: this.actual,
173+
expected: expectedStyle,
174+
message: `Expected the element to match the following style:\n${JSON.stringify(expectedStyle, null, 2)}`,
175+
});
176+
const invertedError = new AssertionError({
177+
actual: this.actual,
178+
message: `Expected the element to NOT match the following style:\n${JSON.stringify(expectedStyle, null, 2)}`,
179+
});
180+
181+
return this.execute({
182+
assertWhen: equal(expectedStyle, receivedStyle),
183+
error,
184+
invertedError,
185+
});
186+
}
200187

201188
/**
202189
* Helper method to assert the presence or absence of class names.
@@ -233,4 +220,8 @@ export class ElementAssertion<T extends Element> extends Assertion<T> {
233220
invertedError,
234221
});
235222
}
223+
224+
private getClassList(): string[] {
225+
return this.actual.className.split(/\s+/).filter(Boolean);
226+
}
236227
}
Lines changed: 13 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,30 @@
11
export interface CssAtRuleAST {
2-
rules: Rule[];
32
declarations: StyleDeclaration[];
3+
rules: Rule[];
44
}
55

66
interface Rule {
7-
selectors: string[];
87
declarations: StyleDeclaration[];
8+
selectors: string[];
99
}
1010

1111
interface StyleDeclaration extends Record<string, string> {
1212
property: string;
1313
value: string;
1414
}
1515

16-
export const normalizeStylesObject = (
17-
css: Object,
18-
window: Window
19-
): { props: string[]; expectedStyle: StyleDeclaration } => {
16+
export const normalizeStyles = (css: Partial<CSSStyleDeclaration>):
17+
{ expectedStyle: StyleDeclaration; props: string[]; } => {
2018
const normalizer = document.createElement("div");
2119
document.body.appendChild(normalizer);
2220

2321
const { props, expectedStyle } = Object.entries(css).reduce(
2422
(acc, [property, value]) => {
23+
24+
if (typeof value !== "string") {
25+
return acc;
26+
}
27+
2528
normalizer.style.setProperty(property, value);
2629

2730
const normalizedValue = window
@@ -30,71 +33,24 @@ export const normalizeStylesObject = (
3033
.trim();
3134

3235
return {
33-
props: [...acc.props, property],
3436
expectedStyle: {
3537
...acc.expectedStyle,
3638
[property]: normalizedValue,
3739
},
40+
props: [...acc.props, property],
3841
};
3942
},
40-
{ props: [] as string[], expectedStyle: {} as StyleDeclaration }
43+
{ expectedStyle: {} as StyleDeclaration, props: [] as string[] },
4144
);
4245

4346
document.body.removeChild(normalizer);
4447

45-
return { props, expectedStyle };
46-
};
47-
48-
export const normalizeStylesString = (expectedRule: CssAtRuleAST, window: Window) => {
49-
const normalizer = document.createElement("div");
50-
document.body.appendChild(normalizer);
51-
52-
const rules = expectedRule?.rules[0] || { declarations: [] };
53-
const { props, expectedStyle } = rules?.declarations.reduce(
54-
(acc, { property, value }) => {
55-
normalizer.style.setProperty(property, value);
56-
57-
const normalizedValue = window
58-
.getComputedStyle(normalizer)
59-
.getPropertyValue(property)
60-
.trim();
61-
62-
return {
63-
props: [...acc.props, property],
64-
expectedStyle: {
65-
...acc.expectedStyle,
66-
[property]: normalizedValue,
67-
},
68-
};
69-
},
70-
{ props: [] as string[], expectedStyle: {} as StyleDeclaration }
71-
);
72-
73-
document.body.removeChild(normalizer);
74-
75-
return { props, expectedStyle };
48+
return { expectedStyle, props };
7649
};
7750

78-
export const getProps = (props : string[], received: CSSStyleDeclaration) => {
51+
export const getReceivedStyle = (props: string[], received: CSSStyleDeclaration): StyleDeclaration => {
7952
return props.reduce((acc, prop) => {
8053
acc[prop] = received?.getPropertyValue(prop).trim();
8154
return acc;
8255
}, {} as StyleDeclaration);
83-
8456
};
85-
86-
export const isSameStyle = (expectedStyle: StyleDeclaration, receivedStyle: StyleDeclaration): boolean => {
87-
return !!Object.keys(expectedStyle).length &&
88-
Object.entries(expectedStyle).every(([expectedProp, expectedValue]) => {
89-
const isCustomProperty = expectedProp.startsWith("--");
90-
const spellingVariants = [expectedProp];
91-
expectedProp !== null;
92-
93-
if (!isCustomProperty)
94-
spellingVariants.push(expectedProp.toLowerCase());
95-
return spellingVariants.some(
96-
(searchProp) => receivedStyle[searchProp] === expectedValue
97-
);
98-
});
99-
}
100-

0 commit comments

Comments
 (0)