From 9e88d393fa15fa1cfbf572a32a6c2baab514d155 Mon Sep 17 00:00:00 2001 From: Sebas Cruz Date: Mon, 6 Oct 2025 15:48:16 -0500 Subject: [PATCH 1/6] Add: toHaveSomeStyle matcher and tests first approach --- packages/dom/src/lib/ElementAssertion.ts | 51 +++++++++++++++++++ .../test/unit/lib/ElementAssertion.test.tsx | 18 +++++++ 2 files changed, 69 insertions(+) diff --git a/packages/dom/src/lib/ElementAssertion.ts b/packages/dom/src/lib/ElementAssertion.ts index c4fc8a6..34f7704 100644 --- a/packages/dom/src/lib/ElementAssertion.ts +++ b/packages/dom/src/lib/ElementAssertion.ts @@ -190,6 +190,57 @@ export class ElementAssertion extends Assertion { }); } + /** + * Asserts that the element has the one of the specified CSS style. + * + * @example + * ``` + * expect(component).toHaveStyle({ color: 'green', display: 'block' }); + * ``` + * + * @param expected the expected CSS styles. + * @returns the assertion instance. + */ + + public toHaveSomeStyle(expected: Partial): this { + + if (!this.actual.ownerDocument.defaultView) { + throw new Error("The element is not attached to a document with a default view."); + } + if (!(this.actual instanceof HTMLElement)) { + throw new Error("The element is not an HTMLElement."); + } + + const window = this.actual.ownerDocument.defaultView; + + const received = window.getComputedStyle(this.actual); + + const { props, expectedStyle } = normalizeStyles(expected); + + const receivedStyle = getReceivedStyle(props, received); + + const a = Object.values(receivedStyle).some((receivedItem, idx) => { + const expectedItem = Object.values(expectedStyle)[idx]; + return equal(expectedItem, receivedItem); + }); + + const error = new AssertionError({ + actual: this.actual, + message: "Error", + }); + + const invertedError = new AssertionError({ + actual: this.actual, + message: "Inverted Error", + }); + + return this.execute({ + assertWhen: a, + error, + invertedError, + }); + } + /** * Helper method to assert the presence or absence of class names. * diff --git a/packages/dom/test/unit/lib/ElementAssertion.test.tsx b/packages/dom/test/unit/lib/ElementAssertion.test.tsx index f91f10c..c226337 100644 --- a/packages/dom/test/unit/lib/ElementAssertion.test.tsx +++ b/packages/dom/test/unit/lib/ElementAssertion.test.tsx @@ -337,4 +337,22 @@ describe("[Unit] ElementAssertion.test.ts", () => { }); }); }); + + describe(".toHaveSomeStyle", () => { + context("when the element contains one or more expected styles", () => { + it("returns the assertion instance", () => { + const { getByTestId } = render( +
, + ); + const divTest = getByTestId("test-div"); + const test = new ElementAssertion(divTest); + + expect(test.toHaveSomeStyle({ color: "red", display: "block" })).toBeEqual(test); + }); + }); + }); }); From 5bedb23650bfddc85a74d9a2e04cc891bfff1a48 Mon Sep 17 00:00:00 2001 From: Sebas Cruz Date: Tue, 7 Oct 2025 11:30:36 -0500 Subject: [PATCH 2/6] Add: function getExepectedAndReceivedStyles refactored and included on matchers --- packages/dom/src/lib/ElementAssertion.ts | 50 ++++++++---------------- packages/dom/src/lib/helpers/helpers.ts | 27 ++++++++++++- 2 files changed, 42 insertions(+), 35 deletions(-) diff --git a/packages/dom/src/lib/ElementAssertion.ts b/packages/dom/src/lib/ElementAssertion.ts index 34f7704..055511e 100644 --- a/packages/dom/src/lib/ElementAssertion.ts +++ b/packages/dom/src/lib/ElementAssertion.ts @@ -1,7 +1,7 @@ import { Assertion, AssertionError } from "@assertive-ts/core"; import equal from "fast-deep-equal"; -import { getReceivedStyle, normalizeStyles } from "./helpers/helpers"; +import { getExpectedAndReceivedStyles } from "./helpers/helpers"; export class ElementAssertion extends Assertion { @@ -158,20 +158,12 @@ export class ElementAssertion extends Assertion { */ public toHaveStyle(expected: Partial): this { - if (!this.actual.ownerDocument.defaultView) { - throw new Error("The element is not attached to a document with a default view."); - } - if (!(this.actual instanceof HTMLElement)) { - throw new Error("The element is not an HTMLElement."); - } - - const window = this.actual.ownerDocument.defaultView; - - const received = window.getComputedStyle(this.actual); - const { props, expectedStyle } = normalizeStyles(expected); + const [expectedStyle, receivedStyle] = getExpectedAndReceivedStyles(this.actual, expected); - const receivedStyle = getReceivedStyle(props, received); + if (!expectedStyle || !receivedStyle) { + throw new Error("No available styles."); + } const error = new AssertionError({ actual: this.actual, @@ -191,51 +183,43 @@ export class ElementAssertion extends Assertion { } /** - * Asserts that the element has the one of the specified CSS style. + * Asserts that the element has one or more of the specified CSS style. * * @example * ``` - * expect(component).toHaveStyle({ color: 'green', display: 'block' }); + * expect(component).toHaveSomeStyle({ color: 'green', display: 'block' }); * ``` * - * @param expected the expected CSS styles. + * @param expected the expected CSS style/s. * @returns the assertion instance. */ public toHaveSomeStyle(expected: Partial): this { - if (!this.actual.ownerDocument.defaultView) { - throw new Error("The element is not attached to a document with a default view."); - } - if (!(this.actual instanceof HTMLElement)) { - throw new Error("The element is not an HTMLElement."); - } - - const window = this.actual.ownerDocument.defaultView; + const [expectedStyle, receivedStyle] = getExpectedAndReceivedStyles(this.actual, expected); - const received = window.getComputedStyle(this.actual); - - const { props, expectedStyle } = normalizeStyles(expected); - - const receivedStyle = getReceivedStyle(props, received); + if (!expectedStyle || !receivedStyle) { + throw new Error("No available styles."); + } - const a = Object.values(receivedStyle).some((receivedItem, idx) => { + const hasSomeStyle = Object.values(receivedStyle).some((receivedItem, idx) => { const expectedItem = Object.values(expectedStyle)[idx]; return equal(expectedItem, receivedItem); }); const error = new AssertionError({ actual: this.actual, - message: "Error", + message: `Expected the element to match some of the following styles:\n${JSON.stringify(expectedStyle, null, 2)}`, }); const invertedError = new AssertionError({ actual: this.actual, - message: "Inverted Error", + // eslint-disable-next-line max-len + message: `Expected the element NOT to match some of the following styles:\n${JSON.stringify(expectedStyle, null, 2)}`, }); return this.execute({ - assertWhen: a, + assertWhen: hasSomeStyle, error, invertedError, }); diff --git a/packages/dom/src/lib/helpers/helpers.ts b/packages/dom/src/lib/helpers/helpers.ts index 843be7d..592442c 100644 --- a/packages/dom/src/lib/helpers/helpers.ts +++ b/packages/dom/src/lib/helpers/helpers.ts @@ -13,7 +13,7 @@ interface StyleDeclaration extends Record { value: string; } -export const normalizeStyles = (css: Partial): +const normalizeStyles = (css: Partial): { expectedStyle: StyleDeclaration; props: string[]; } => { const normalizer = document.createElement("div"); document.body.appendChild(normalizer); @@ -48,9 +48,32 @@ export const normalizeStyles = (css: Partial): return { expectedStyle, props }; }; -export const getReceivedStyle = (props: string[], received: CSSStyleDeclaration): StyleDeclaration => { +const getReceivedStyle = (props: string[], received: CSSStyleDeclaration): StyleDeclaration => { return props.reduce((acc, prop) => { acc[prop] = received?.getPropertyValue(prop).trim(); return acc; }, {} as StyleDeclaration); }; + +export const getExpectedAndReceivedStyles = +(actual: Element, expected: Partial): StyleDeclaration[] => { + if (!actual.ownerDocument.defaultView) { + throw new Error("The element is not attached to a document with a default view."); + } + if (!(actual instanceof HTMLElement)) { + throw new Error("The element is not an HTMLElement."); + } + + const window = actual.ownerDocument.defaultView; + + const received = window.getComputedStyle(actual); + + const { props, expectedStyle } = normalizeStyles(expected); + + const receivedStyle = getReceivedStyle(props, received); + + return [ + expectedStyle, + receivedStyle, + ]; +}; From fef46cf101c3cc68d99560947d95556fc221e732 Mon Sep 17 00:00:00 2001 From: Sebas Cruz Date: Tue, 7 Oct 2025 11:31:15 -0500 Subject: [PATCH 3/6] Add: tests for toHaveSomeStyles --- .../test/unit/lib/ElementAssertion.test.tsx | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/packages/dom/test/unit/lib/ElementAssertion.test.tsx b/packages/dom/test/unit/lib/ElementAssertion.test.tsx index c226337..d4f4dc2 100644 --- a/packages/dom/test/unit/lib/ElementAssertion.test.tsx +++ b/packages/dom/test/unit/lib/ElementAssertion.test.tsx @@ -351,7 +351,33 @@ describe("[Unit] ElementAssertion.test.ts", () => { const divTest = getByTestId("test-div"); const test = new ElementAssertion(divTest); - expect(test.toHaveSomeStyle({ color: "red", display: "block" })).toBeEqual(test); + expect(test.toHaveSomeStyle({ color: "blue", display: "flex" })).toBeEqual(test); + + expect(() => test.not.toHaveSomeStyle({ color: "blue" })) + .toThrowError(AssertionError) + // eslint-disable-next-line max-len + .toHaveMessage("Expected the element NOT to match some of the following styles:\n{\n \"color\": \"rgb(0, 0, 255)\"\n}"); + }); + }); + + context("when the element does not contain any of the expected styles", () => { + it("throws an assertion error", () => { + const { getByTestId } = render( +
, + ); + const divTest = getByTestId("test-div"); + const test = new ElementAssertion(divTest); + + expect(() => test.toHaveSomeStyle({ color: "red", display: "flex" })) + .toThrowError(AssertionError) + // eslint-disable-next-line max-len + .toHaveMessage("Expected the element to match some of the following styles:\n{\n \"color\": \"rgb(255, 0, 0)\",\n \"display\": \"flex\"\n}"); + + expect(test.not.toHaveSomeStyle({ border: "1px solid blue", color: "red", display: "flex" })).toBeEqual(test); }); }); }); From b5b4148072bd0fabb2277368a80bc8ac6dd565a2 Mon Sep 17 00:00:00 2001 From: Sebas Cruz Date: Mon, 20 Oct 2025 13:41:58 -0500 Subject: [PATCH 4/6] Add: properties to test --- packages/dom/test/unit/lib/ElementAssertion.test.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/dom/test/unit/lib/ElementAssertion.test.tsx b/packages/dom/test/unit/lib/ElementAssertion.test.tsx index d4f4dc2..3719659 100644 --- a/packages/dom/test/unit/lib/ElementAssertion.test.tsx +++ b/packages/dom/test/unit/lib/ElementAssertion.test.tsx @@ -343,15 +343,14 @@ describe("[Unit] ElementAssertion.test.ts", () => { it("returns the assertion instance", () => { const { getByTestId } = render(
, ); const divTest = getByTestId("test-div"); const test = new ElementAssertion(divTest); - expect(test.toHaveSomeStyle({ color: "blue", display: "flex" })).toBeEqual(test); + expect(test.toHaveSomeStyle({ color: "red", display: "flex", height: "3rem", width: "2rem" })).toBeEqual(test); expect(() => test.not.toHaveSomeStyle({ color: "blue" })) .toThrowError(AssertionError) From df1fd8b8b40acf41b369e166a3a0c81c1b18dc75 Mon Sep 17 00:00:00 2001 From: Sebas Cruz Date: Mon, 20 Oct 2025 13:43:08 -0500 Subject: [PATCH 5/6] Add: getReceivedStyle function enhanced --- packages/dom/src/lib/ElementAssertion.ts | 13 +++++++------ packages/dom/src/lib/helpers/helpers.ts | 12 ++++++++---- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/packages/dom/src/lib/ElementAssertion.ts b/packages/dom/src/lib/ElementAssertion.ts index 055511e..db81dd6 100644 --- a/packages/dom/src/lib/ElementAssertion.ts +++ b/packages/dom/src/lib/ElementAssertion.ts @@ -183,7 +183,7 @@ export class ElementAssertion extends Assertion { } /** - * Asserts that the element has one or more of the specified CSS style. + * Asserts that the element has one or more of the specified CSS styles. * * @example * ``` @@ -196,15 +196,16 @@ export class ElementAssertion extends Assertion { public toHaveSomeStyle(expected: Partial): this { - const [expectedStyle, receivedStyle] = getExpectedAndReceivedStyles(this.actual, expected); + const [expectedStyle, elementProcessedStyle] = getExpectedAndReceivedStyles(this.actual, expected); - if (!expectedStyle || !receivedStyle) { + if (!expectedStyle || !elementProcessedStyle) { throw new Error("No available styles."); } - const hasSomeStyle = Object.values(receivedStyle).some((receivedItem, idx) => { - const expectedItem = Object.values(expectedStyle)[idx]; - return equal(expectedItem, receivedItem); + const hasSomeStyle = Object.entries(expectedStyle).some(([expectedProp, expectedValue]) => { + return Object.entries(elementProcessedStyle).some(([receivedProp, receivedValue]) => { + return equal(expectedProp, receivedProp) && equal(expectedValue, receivedValue); + }); }); const error = new AssertionError({ diff --git a/packages/dom/src/lib/helpers/helpers.ts b/packages/dom/src/lib/helpers/helpers.ts index 592442c..197cfd0 100644 --- a/packages/dom/src/lib/helpers/helpers.ts +++ b/packages/dom/src/lib/helpers/helpers.ts @@ -50,7 +50,11 @@ const normalizeStyles = (css: Partial): const getReceivedStyle = (props: string[], received: CSSStyleDeclaration): StyleDeclaration => { return props.reduce((acc, prop) => { - acc[prop] = received?.getPropertyValue(prop).trim(); + const actualStyle = received.getPropertyValue(prop).trim(); + if (!actualStyle) { + return acc; + } + acc[prop] = actualStyle; return acc; }, {} as StyleDeclaration); }; @@ -66,14 +70,14 @@ export const getExpectedAndReceivedStyles = const window = actual.ownerDocument.defaultView; - const received = window.getComputedStyle(actual); + const rawElementStyles = window.getComputedStyle(actual); const { props, expectedStyle } = normalizeStyles(expected); - const receivedStyle = getReceivedStyle(props, received); + const elementProcessedStyle = getReceivedStyle(props, rawElementStyles); return [ expectedStyle, - receivedStyle, + elementProcessedStyle, ]; }; From 1f5220c158ba31de7984d3f6a76b921ef64736d4 Mon Sep 17 00:00:00 2001 From: Sebas Cruz Date: Wed, 29 Oct 2025 14:29:12 -0500 Subject: [PATCH 6/6] Add: messaging for first validation improved --- packages/dom/src/lib/ElementAssertion.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dom/src/lib/ElementAssertion.ts b/packages/dom/src/lib/ElementAssertion.ts index db81dd6..e89aa48 100644 --- a/packages/dom/src/lib/ElementAssertion.ts +++ b/packages/dom/src/lib/ElementAssertion.ts @@ -162,7 +162,7 @@ export class ElementAssertion extends Assertion { const [expectedStyle, receivedStyle] = getExpectedAndReceivedStyles(this.actual, expected); if (!expectedStyle || !receivedStyle) { - throw new Error("No available styles."); + throw new Error("Currently there are no available styles."); } const error = new AssertionError({