From 25b87a72f6205b633fccea0950451d99e3dea009 Mon Sep 17 00:00:00 2001 From: Sebas Cruz Date: Fri, 1 Aug 2025 14:04:57 -0500 Subject: [PATCH 01/17] Chore: added parse lib from adobe --- packages/dom/package.json | 1 + yarn.lock | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/packages/dom/package.json b/packages/dom/package.json index 407cac0b..9c805333 100644 --- a/packages/dom/package.json +++ b/packages/dom/package.json @@ -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" }, diff --git a/yarn.lock b/yarn.lock index 6d0e447c..d0f35bac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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" @@ -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" From ec10ba5c82d84d9ef8056db5ca1c8fee6f3e448d Mon Sep 17 00:00:00 2001 From: Sebas Cruz Date: Fri, 1 Aug 2025 14:07:51 -0500 Subject: [PATCH 02/17] Add: toHaveStyle first approach --- packages/dom/src/lib/ElementAssertion.ts | 72 ++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/packages/dom/src/lib/ElementAssertion.ts b/packages/dom/src/lib/ElementAssertion.ts index 537d8084..57af733c 100644 --- a/packages/dom/src/lib/ElementAssertion.ts +++ b/packages/dom/src/lib/ElementAssertion.ts @@ -1,4 +1,5 @@ import { Assertion, AssertionError } from "@assertive-ts/core"; +import {parse} from '@adobe/css-tools' export class ElementAssertion extends Assertion { @@ -146,6 +147,77 @@ export class ElementAssertion extends Assertion { return this.actual.className.split(/\s+/).filter(Boolean); } + public toHaveStyle(css: Object|string): this { + const styleTest = document.createElement("div"); + styleTest.style.color = "red"; + styleTest.style.display = "flex"; + 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 + const received = computedStyle?.(this.actual); + console.log(received?.color); + const expectedRule = expected.rules[0]; + + interface StyleDeclaration { + property: string; + value: string; + } + + let style = {} + let props: string[] = [] + + expectedRule.declarations.map((declaration: StyleDeclaration) => { + const property = declaration.property; + const value = declaration.value; + + props = [...props, property]; + + style = { + ...style, + [property]: value, + }; + + return style + + }) + + console.log(style); + console.log(props); + + props.map((prop: string) => { + + console.log(received?.[prop]); + }) + + + return this.execute({ + assertWhen: true, + error: new AssertionError({ + actual: this.actual, + message: "Expected the element to have the specified style", + }), + invertedError: new AssertionError({ + actual: this.actual, + message: "Expected the element to NOT have the specified style", + }), + }); + } + return this; + } + + /** * Helper method to assert the presence or absence of class names. * From c66b17f3e4ab5fe4e8031ac137d5fc4173aaa824 Mon Sep 17 00:00:00 2001 From: Sebas Cruz Date: Fri, 1 Aug 2025 14:08:16 -0500 Subject: [PATCH 03/17] Add: toHaveStyle tests first approach --- .../dom/test/unit/lib/ElementAssertion.test.tsx | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/dom/test/unit/lib/ElementAssertion.test.tsx b/packages/dom/test/unit/lib/ElementAssertion.test.tsx index 47bb1674..e1a0b1d2 100644 --- a/packages/dom/test/unit/lib/ElementAssertion.test.tsx +++ b/packages/dom/test/unit/lib/ElementAssertion.test.tsx @@ -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"; @@ -265,4 +265,17 @@ describe("[Unit] ElementAssertion.test.ts", () => { }); }); -}); + describe(".toHaveStyle", () => { + context("when the element has the expected style when passed as object", () => { + it("returns the assertion instance when it receives an object", () => { + const { getByTestId } = render(
); + const divTest = getByTestId("test-div"); + const test = new ElementAssertion(divTest); + + expect(test.toHaveStyle("display: flex; color: red")).toBeEqual(test); + + }); + }); +}) + +}) From 6efcdef14fd9f68350e2d2bc3b3a3b49b4fa2e3f Mon Sep 17 00:00:00 2001 From: Sebas Cruz Date: Fri, 8 Aug 2025 15:22:10 -0500 Subject: [PATCH 04/17] Refactor: toHaveStyle class --- packages/dom/src/lib/ElementAssertion.ts | 53 +++++++++++++++++------- 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/packages/dom/src/lib/ElementAssertion.ts b/packages/dom/src/lib/ElementAssertion.ts index 57af733c..2fceecad 100644 --- a/packages/dom/src/lib/ElementAssertion.ts +++ b/packages/dom/src/lib/ElementAssertion.ts @@ -167,7 +167,6 @@ export class ElementAssertion extends Assertion { const expected = parsedCSS const received = computedStyle?.(this.actual); - console.log(received?.color); const expectedRule = expected.rules[0]; interface StyleDeclaration { @@ -175,32 +174,58 @@ export class ElementAssertion extends Assertion { value: string; } - let style = {} + let expectedStyle = {} + let receivedStyle = {} let props: string[] = [] + const normalizer = document.createElement("div"); + document.body.appendChild(normalizer); + + expectedRule.declarations.map((declaration: StyleDeclaration) => { const property = declaration.property; const value = declaration.value; - + props = [...props, property]; - - style = { - ...style, - [property]: value, + + normalizer.style[property] = value; + const normalizedValue = window.getComputedStyle(normalizer).getPropertyValue(property); + + expectedStyle = { + ...expectedStyle, + [property]: normalizedValue.trim(), }; - - return style - }) + return expectedStyle; + }); + + document.body.removeChild(normalizer); - console.log(style); - console.log(props); + + console.log("expected style: ",expectedStyle); props.map((prop: string) => { - - console.log(received?.[prop]); + 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 + ) + }) + + console.log("isSameStyle: ", isSameStyle) return this.execute({ assertWhen: true, From 7b0938b0815922be445c8a3c6beb64b228e21ab8 Mon Sep 17 00:00:00 2001 From: Sebas Cruz Date: Fri, 8 Aug 2025 15:22:53 -0500 Subject: [PATCH 05/17] Add: test for when styles are the same and the opposite case --- .../test/unit/lib/ElementAssertion.test.tsx | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/packages/dom/test/unit/lib/ElementAssertion.test.tsx b/packages/dom/test/unit/lib/ElementAssertion.test.tsx index e1a0b1d2..cbdc0a58 100644 --- a/packages/dom/test/unit/lib/ElementAssertion.test.tsx +++ b/packages/dom/test/unit/lib/ElementAssertion.test.tsx @@ -266,16 +266,41 @@ 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(
); + 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(
); + 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 it receives an object", () => { + it("returns the assertion instance when the styles are the same", () => { + const { getByTestId } = render(
); + 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(
); const divTest = getByTestId("test-div"); const test = new ElementAssertion(divTest); - expect(test.toHaveStyle("display: flex; color: red")).toBeEqual(test); + expect(test.toHaveStyle({color: "red", display: "flex", border: "1px solid black"})).toBeEqual(test); }); }); }) - +}) }) From ab11999234e0decd1205ad35d0d0351daaf7c1ca Mon Sep 17 00:00:00 2001 From: Sebas Cruz Date: Tue, 12 Aug 2025 10:33:02 -0500 Subject: [PATCH 06/17] Add: Funcionality for object cases --- packages/dom/src/lib/ElementAssertion.ts | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/packages/dom/src/lib/ElementAssertion.ts b/packages/dom/src/lib/ElementAssertion.ts index 2fceecad..d6b0724b 100644 --- a/packages/dom/src/lib/ElementAssertion.ts +++ b/packages/dom/src/lib/ElementAssertion.ts @@ -148,14 +148,10 @@ export class ElementAssertion extends Assertion { } public toHaveStyle(css: Object|string): this { - const styleTest = document.createElement("div"); - styleTest.style.color = "red"; - styleTest.style.display = "flex"; if ( this.actual instanceof HTMLElement || this.actual['ownerDocument'] ) { - const parsedCSS = typeof css === 'object' ? css @@ -166,8 +162,8 @@ export class ElementAssertion extends Assertion { const computedStyle = window?.getComputedStyle; const expected = parsedCSS + console.log("expected: ", expected); const received = computedStyle?.(this.actual); - const expectedRule = expected.rules[0]; interface StyleDeclaration { property: string; @@ -181,7 +177,23 @@ export class ElementAssertion extends Assertion { 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(), + }; + + }); + console.log("EXPECTED STYLE: ", expectedStyle); + } else { + const expectedRule = expected.rules[0]; expectedRule.declarations.map((declaration: StyleDeclaration) => { const property = declaration.property; const value = declaration.value; @@ -198,6 +210,7 @@ export class ElementAssertion extends Assertion { return expectedStyle; }); + } document.body.removeChild(normalizer); From 5ba57a6a546387f18a678852bf1b8df799adea67 Mon Sep 17 00:00:00 2001 From: Sebas Cruz Date: Tue, 12 Aug 2025 10:50:05 -0500 Subject: [PATCH 07/17] Add: Assertion execution --- packages/dom/src/lib/ElementAssertion.ts | 21 +++++++++++-------- .../test/unit/lib/ElementAssertion.test.tsx | 2 +- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/dom/src/lib/ElementAssertion.ts b/packages/dom/src/lib/ElementAssertion.ts index d6b0724b..0378a6b6 100644 --- a/packages/dom/src/lib/ElementAssertion.ts +++ b/packages/dom/src/lib/ElementAssertion.ts @@ -239,17 +239,20 @@ export class ElementAssertion extends Assertion { }) console.log("isSameStyle: ", isSameStyle) - - return this.execute({ - assertWhen: true, - error: new AssertionError({ + const error = new AssertionError({ actual: this.actual, - message: "Expected the element to have the specified style", - }), - invertedError: new AssertionError({ + message: `Expected the element to have ${JSON.stringify(expectedStyle)} style`, + expected: expectedStyle + }) + const invertedError = new AssertionError({ actual: this.actual, - message: "Expected the element to NOT have the specified style", - }), + message: `Expected the element to NOT have ${JSON.stringify(expectedStyle)} style`, + }) + + return this.execute({ + assertWhen: isSameStyle, + error, + invertedError }); } return this; diff --git a/packages/dom/test/unit/lib/ElementAssertion.test.tsx b/packages/dom/test/unit/lib/ElementAssertion.test.tsx index cbdc0a58..c244a27d 100644 --- a/packages/dom/test/unit/lib/ElementAssertion.test.tsx +++ b/packages/dom/test/unit/lib/ElementAssertion.test.tsx @@ -272,7 +272,7 @@ describe("[Unit] ElementAssertion.test.ts", () => { const divTest = getByTestId("test-div"); const test = new ElementAssertion(divTest); - expect(test.toHaveStyle("color: red; display: flex; border: 1px solid black")).toBeEqual(test); + expect(test.toHaveStyle("display: flex; color: red; border: 1px solid black")).toBeEqual(test); }); it("fails the assertion when the styles are not the same", () => { From b0573a5ea4c33b16075c36cb529c90017082d190 Mon Sep 17 00:00:00 2001 From: Sebas Cruz Date: Wed, 27 Aug 2025 13:36:32 -0500 Subject: [PATCH 08/17] Add: functions moved into helpers and implemeneted in class --- packages/dom/src/lib/ElementAssertion.ts | 135 +++++++---------------- packages/dom/src/lib/helpers/helpers.ts | 100 +++++++++++++++++ 2 files changed, 137 insertions(+), 98 deletions(-) create mode 100644 packages/dom/src/lib/helpers/helpers.ts diff --git a/packages/dom/src/lib/ElementAssertion.ts b/packages/dom/src/lib/ElementAssertion.ts index 0378a6b6..ebb4c966 100644 --- a/packages/dom/src/lib/ElementAssertion.ts +++ b/packages/dom/src/lib/ElementAssertion.ts @@ -1,5 +1,6 @@ import { Assertion, AssertionError } from "@assertive-ts/core"; -import {parse} from '@adobe/css-tools' +import { parse } from "@adobe/css-tools"; +import { CssAtRuleAST, getProps, isSameStyle, normalizeStylesObject, normalizeStylesString } from "./helpers/helpers"; export class ElementAssertion extends Assertion { @@ -147,112 +148,50 @@ export class ElementAssertion extends Assertion { 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[] = [] - - 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); + /** + * Asserts that the element has the specified CSS styles. + * + * @param css - The expected CSS styles. + * @returns The assertion instance. + */ - expectedStyle = { - ...expectedStyle, - [property]: normalizedValue?.trim(), + 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; - }); - 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); + const expected = parsedCSS as CssAtRuleAST; + const received = computedStyle?.(this.actual) as CSSStyleDeclaration; - expectedStyle = { - ...expectedStyle, - [property]: normalizedValue.trim(), - }; - return expectedStyle; - }); - } - - 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 { props, expectedStyle } = + typeof css === "object" + ? normalizeStylesObject(css, window!) + : normalizeStylesString(expected, window!); - 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 - ) - }) - - console.log("isSameStyle: ", isSameStyle) - const error = new AssertionError({ - actual: this.actual, - message: `Expected the 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`, - }) + const receivedStyle = getProps(props, received); + const error = new AssertionError({ + actual: this.actual, + message: `Expected the 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, + assertWhen: isSameStyle(expectedStyle, receivedStyle), error, - invertedError + invertedError, }); } return this; diff --git a/packages/dom/src/lib/helpers/helpers.ts b/packages/dom/src/lib/helpers/helpers.ts new file mode 100644 index 00000000..632ea628 --- /dev/null +++ b/packages/dom/src/lib/helpers/helpers.ts @@ -0,0 +1,100 @@ +export interface CssAtRuleAST { + rules: Rule[]; + declarations: StyleDeclaration[]; +} + +interface Rule { + selectors: string[]; + declarations: StyleDeclaration[]; +} + +interface StyleDeclaration extends Record { + property: string; + value: string; +} + +export const normalizeStylesObject = ( + css: Object, + window: Window +): { props: string[]; expectedStyle: StyleDeclaration } => { + const normalizer = document.createElement("div"); + document.body.appendChild(normalizer); + + const { props, expectedStyle } = Object.entries(css).reduce( + (acc, [property, value]) => { + normalizer.style.setProperty(property, value); + + const normalizedValue = window + .getComputedStyle(normalizer) + .getPropertyValue(property) + .trim(); + + return { + props: [...acc.props, property], + expectedStyle: { + ...acc.expectedStyle, + [property]: normalizedValue, + }, + }; + }, + { props: [] as string[], expectedStyle: {} as StyleDeclaration } + ); + + document.body.removeChild(normalizer); + + return { props, expectedStyle }; +}; + +export const normalizeStylesString = (expectedRule: CssAtRuleAST, window: Window) => { + const normalizer = document.createElement("div"); + document.body.appendChild(normalizer); + + const rules = expectedRule?.rules[0] || { declarations: [] }; + const { props, expectedStyle } = rules?.declarations.reduce( + (acc, { property, value }) => { + normalizer.style.setProperty(property, value); + + const normalizedValue = window + .getComputedStyle(normalizer) + .getPropertyValue(property) + .trim(); + + return { + props: [...acc.props, property], + expectedStyle: { + ...acc.expectedStyle, + [property]: normalizedValue, + }, + }; + }, + { props: [] as string[], expectedStyle: {} as StyleDeclaration } + ); + + document.body.removeChild(normalizer); + + return { props, expectedStyle }; +}; + +export const getProps = (props : string[], received: CSSStyleDeclaration) => { + return props.reduce((acc, prop) => { + acc[prop] = received?.getPropertyValue(prop).trim(); + return acc; + }, {} as StyleDeclaration); + +}; + +export const isSameStyle = (expectedStyle: StyleDeclaration, receivedStyle: StyleDeclaration): boolean => { + return !!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 + ); + }); +} + From 2481a747438718bce07a13221387fc90e4c93f9f Mon Sep 17 00:00:00 2001 From: Sebas Cruz Date: Wed, 27 Aug 2025 13:37:18 -0500 Subject: [PATCH 09/17] Add: refactored test --- .../test/unit/lib/ElementAssertion.test.tsx | 90 +++++++++++-------- 1 file changed, 55 insertions(+), 35 deletions(-) diff --git a/packages/dom/test/unit/lib/ElementAssertion.test.tsx b/packages/dom/test/unit/lib/ElementAssertion.test.tsx index c244a27d..181ae6f9 100644 --- a/packages/dom/test/unit/lib/ElementAssertion.test.tsx +++ b/packages/dom/test/unit/lib/ElementAssertion.test.tsx @@ -1,5 +1,5 @@ import { AssertionError, expect } from "@assertive-ts/core"; -import { getByTestId, render } from "@testing-library/react"; +import { render } from "@testing-library/react"; import { ElementAssertion } from "../../../src/lib/ElementAssertion"; @@ -257,50 +257,70 @@ describe("[Unit] ElementAssertion.test.ts", () => { const test = new ElementAssertion(divTest); expect(() => test.toHaveAllClasses("foo", "bar", "baz")) - .toThrowError(AssertionError) - .toHaveMessage('Expected the element to have all of these classes: "foo bar baz"'); - + .toThrowError(AssertionError) + .toHaveMessage('Expected the element to have all of these classes: "foo bar baz"'); + expect(test.not.toHaveAllClasses("foo", "bar", "baz")).toBeEqual(test); }); }); }); - + 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(
); - 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(
); - const divTest = getByTestId("test-div"); - const test = new ElementAssertion(divTest); + context("when the style is passed as a string", () => { + context("and the element has the expected style", () => { + it("returns the assertion instance", () => { + const { getByTestId } = render(
); + const divTest = getByTestId("test-div"); + const test = new ElementAssertion(divTest); + + expect(test.toHaveStyle("display: flex; color: red; border: 1px solid black")).toBeEqual(test); + + expect(() => test.not.toHaveStyle("display: flex; color: red; border: 1px solid black")) + .toThrowError(AssertionError) + .toHaveMessage('Expected the element to NOT have {"display":"flex","color":"rgb(255, 0, 0)","border":"1px solid black"} style'); + }); + }); - expect(test.toHaveStyle("color: red; display: flex; border: 1px solid black;")).toBeEqual(test); + context("and the element does not have the expected style", () => { + it("throws an assertion error", () => { + const { getByTestId } = render(
); + const divTest = getByTestId("test-div"); + const test = new ElementAssertion(divTest); + + expect(test.not.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(
); - 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(
); - const divTest = getByTestId("test-div"); - const test = new ElementAssertion(divTest); + context("when the style is passed as an object", () => { + context("and the element has the expected style", () => { + it("returns the assertion instance", () => { + const { getByTestId } = render(
); + const divTest = getByTestId("test-div"); + const test = new ElementAssertion(divTest); + + expect(test.toHaveStyle({color: "red", display: "flex", border: "1px solid black"})).toBeEqual(test); - expect(test.toHaveStyle({color: "red", display: "flex", border: "1px solid black"})).toBeEqual(test); + expect(() => test.not.toHaveStyle({color: "red", display: "flex", border: "1px solid black"})) + .toThrowError(AssertionError) + .toHaveMessage('Expected the element to NOT have {"color":"rgb(255, 0, 0)","display":"flex","border":"1px solid black"} style'); + + }); + }); - }); + context("and the element does not have the expected style", () => { + it("throws an assertion error", () => { + const { getByTestId } = render(
); + const divTest = getByTestId("test-div"); + const test = new ElementAssertion(divTest); + + expect(() => test.toHaveStyle(({ color: "red", display: "flex", border: "1px solid black" }))) + .toThrowError(AssertionError) + .toHaveMessage("Expected the element to have {\"color\":\"rgb(255, 0, 0)\",\"display\":\"flex\",\"border\":\"1px solid black\"} style"); + + }); + }); + }); }); }) -}) -}) From 33320b660238a9aa69d372140c9e5284f4d381e2 Mon Sep 17 00:00:00 2001 From: Sebas Cruz Date: Mon, 1 Sep 2025 15:36:31 -0500 Subject: [PATCH 10/17] Add: simplified logic and refactored functions --- packages/dom/src/lib/ElementAssertion.ts | 85 +++++++++++------------- packages/dom/src/lib/helpers/helpers.ts | 70 ++++--------------- 2 files changed, 51 insertions(+), 104 deletions(-) diff --git a/packages/dom/src/lib/ElementAssertion.ts b/packages/dom/src/lib/ElementAssertion.ts index ebb4c966..d4790277 100644 --- a/packages/dom/src/lib/ElementAssertion.ts +++ b/packages/dom/src/lib/ElementAssertion.ts @@ -1,6 +1,7 @@ import { Assertion, AssertionError } from "@assertive-ts/core"; -import { parse } from "@adobe/css-tools"; -import { CssAtRuleAST, getProps, isSameStyle, normalizeStylesObject, normalizeStylesString } from "./helpers/helpers"; +import equal from "fast-deep-equal"; + +import { getReceivedStyle, normalizeStyles } from "./helpers/helpers"; export class ElementAssertion extends Assertion { @@ -144,59 +145,45 @@ export class ElementAssertion extends Assertion { ); } - private getClassList(): string[] { - return this.actual.className.split(/\s+/).filter(Boolean); - } - /** * Asserts that the element has the specified CSS styles. * - * @param css - The expected CSS styles. + * @param expected - The expected CSS styles. * @returns The assertion instance. */ - 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 as CssAtRuleAST; - const received = computedStyle?.(this.actual) as CSSStyleDeclaration; - - - const { props, expectedStyle } = - typeof css === "object" - ? normalizeStylesObject(css, window!) - : normalizeStylesString(expected, window!); - - const receivedStyle = getProps(props, received); - - const error = new AssertionError({ - actual: this.actual, - message: `Expected the 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(expectedStyle, receivedStyle), - error, - invertedError, - }); + 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."); } - return this; - } + const window = this.actual.ownerDocument.defaultView; + + const received = window.getComputedStyle(this.actual); + + const { props, expectedStyle } = normalizeStyles(expected); + + const receivedStyle = getReceivedStyle(props, received); + + const error = new AssertionError({ + actual: this.actual, + expected: expectedStyle, + message: `Expected the element to match the following style:\n${JSON.stringify(expectedStyle, null, 2)}`, + }); + const invertedError = new AssertionError({ + actual: this.actual, + message: `Expected the element to NOT match the following style:\n${JSON.stringify(expectedStyle, null, 2)}`, + }); + + return this.execute({ + assertWhen: equal(expectedStyle, receivedStyle), + error, + invertedError, + }); + } /** * Helper method to assert the presence or absence of class names. @@ -233,4 +220,8 @@ export class ElementAssertion extends Assertion { invertedError, }); } + + private getClassList(): string[] { + return this.actual.className.split(/\s+/).filter(Boolean); + } } diff --git a/packages/dom/src/lib/helpers/helpers.ts b/packages/dom/src/lib/helpers/helpers.ts index 632ea628..843be7d9 100644 --- a/packages/dom/src/lib/helpers/helpers.ts +++ b/packages/dom/src/lib/helpers/helpers.ts @@ -1,11 +1,11 @@ export interface CssAtRuleAST { - rules: Rule[]; declarations: StyleDeclaration[]; + rules: Rule[]; } interface Rule { - selectors: string[]; declarations: StyleDeclaration[]; + selectors: string[]; } interface StyleDeclaration extends Record { @@ -13,15 +13,18 @@ interface StyleDeclaration extends Record { value: string; } -export const normalizeStylesObject = ( - css: Object, - window: Window -): { props: string[]; expectedStyle: StyleDeclaration } => { +export const normalizeStyles = (css: Partial): +{ expectedStyle: StyleDeclaration; props: string[]; } => { const normalizer = document.createElement("div"); document.body.appendChild(normalizer); const { props, expectedStyle } = Object.entries(css).reduce( (acc, [property, value]) => { + + if (typeof value !== "string") { + return acc; + } + normalizer.style.setProperty(property, value); const normalizedValue = window @@ -30,71 +33,24 @@ export const normalizeStylesObject = ( .trim(); return { - props: [...acc.props, property], expectedStyle: { ...acc.expectedStyle, [property]: normalizedValue, }, + props: [...acc.props, property], }; }, - { props: [] as string[], expectedStyle: {} as StyleDeclaration } + { expectedStyle: {} as StyleDeclaration, props: [] as string[] }, ); document.body.removeChild(normalizer); - return { props, expectedStyle }; -}; - -export const normalizeStylesString = (expectedRule: CssAtRuleAST, window: Window) => { - const normalizer = document.createElement("div"); - document.body.appendChild(normalizer); - - const rules = expectedRule?.rules[0] || { declarations: [] }; - const { props, expectedStyle } = rules?.declarations.reduce( - (acc, { property, value }) => { - normalizer.style.setProperty(property, value); - - const normalizedValue = window - .getComputedStyle(normalizer) - .getPropertyValue(property) - .trim(); - - return { - props: [...acc.props, property], - expectedStyle: { - ...acc.expectedStyle, - [property]: normalizedValue, - }, - }; - }, - { props: [] as string[], expectedStyle: {} as StyleDeclaration } - ); - - document.body.removeChild(normalizer); - - return { props, expectedStyle }; + return { expectedStyle, props }; }; -export const getProps = (props : string[], received: CSSStyleDeclaration) => { +export const getReceivedStyle = (props: string[], received: CSSStyleDeclaration): StyleDeclaration => { return props.reduce((acc, prop) => { acc[prop] = received?.getPropertyValue(prop).trim(); return acc; }, {} as StyleDeclaration); - }; - -export const isSameStyle = (expectedStyle: StyleDeclaration, receivedStyle: StyleDeclaration): boolean => { - return !!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 - ); - }); -} - From 53f1d32527899c805fc85cf8a2d5484aacf9e52b Mon Sep 17 00:00:00 2001 From: Sebas Cruz Date: Fri, 3 Oct 2025 12:03:37 -0500 Subject: [PATCH 11/17] Add: test for partial match --- packages/dom/src/lib/ElementAssertion.ts | 9 +- .../test/unit/lib/ElementAssertion.test.tsx | 102 ++++++++++-------- 2 files changed, 65 insertions(+), 46 deletions(-) diff --git a/packages/dom/src/lib/ElementAssertion.ts b/packages/dom/src/lib/ElementAssertion.ts index d4790277..c4fc8a62 100644 --- a/packages/dom/src/lib/ElementAssertion.ts +++ b/packages/dom/src/lib/ElementAssertion.ts @@ -148,8 +148,13 @@ export class ElementAssertion extends Assertion { /** * Asserts that the element has the specified CSS styles. * - * @param expected - The expected CSS styles. - * @returns The assertion instance. + * @example + * ``` + * expect(component).toHaveStyle({ color: 'green', display: 'block' }); + * ``` + * + * @param expected the expected CSS styles. + * @returns the assertion instance. */ public toHaveStyle(expected: Partial): this { diff --git a/packages/dom/test/unit/lib/ElementAssertion.test.tsx b/packages/dom/test/unit/lib/ElementAssertion.test.tsx index 181ae6f9..f91f10cd 100644 --- a/packages/dom/test/unit/lib/ElementAssertion.test.tsx +++ b/packages/dom/test/unit/lib/ElementAssertion.test.tsx @@ -259,68 +259,82 @@ describe("[Unit] ElementAssertion.test.ts", () => { expect(() => test.toHaveAllClasses("foo", "bar", "baz")) .toThrowError(AssertionError) .toHaveMessage('Expected the element to have all of these classes: "foo bar baz"'); - + expect(test.not.toHaveAllClasses("foo", "bar", "baz")).toBeEqual(test); }); }); }); - + describe(".toHaveStyle", () => { - context("when the style is passed as a string", () => { - context("and the element has the expected style", () => { - it("returns the assertion instance", () => { - const { getByTestId } = render(
); - const divTest = getByTestId("test-div"); - const test = new ElementAssertion(divTest); - - expect(test.toHaveStyle("display: flex; color: red; border: 1px solid black")).toBeEqual(test); - - expect(() => test.not.toHaveStyle("display: flex; color: red; border: 1px solid black")) - .toThrowError(AssertionError) - .toHaveMessage('Expected the element to NOT have {"display":"flex","color":"rgb(255, 0, 0)","border":"1px solid black"} style'); - }); + context("when the element has the expected style", () => { + it("returns the assertion instance", () => { + const { getByTestId } = render( +
); + const divTest = getByTestId("test-div"); + const test = new ElementAssertion(divTest); + + expect(test.toHaveStyle({ border: "1px solid black", color: "red", display: "flex" })).toBeEqual(test); + + expect(() => test.not.toHaveStyle({ border: "1px solid black", color: "red", display: "flex" })) + .toThrowError(AssertionError) + .toHaveMessage( + // eslint-disable-next-line max-len + 'Expected the element to NOT match the following style:\n{\n "border": "1px solid black",\n "color": "rgb(255, 0, 0)",\n "display": "flex"\n}', + ); }); + }); - context("and the element does not have the expected style", () => { + context("when the element does not have the expected style", () => { it("throws an assertion error", () => { - const { getByTestId } = render(
); + const { getByTestId } = render( +
, + ); + const divTest = getByTestId("test-div"); const test = new ElementAssertion(divTest); - - expect(test.not.toHaveStyle("color: red; display: flex; border: 1px solid black;")).toBeEqual(test); + + expect(() => test.toHaveStyle(({ border: "1px solid black", color: "red", display: "flex" }))) + .toThrowError(AssertionError) + .toHaveMessage( + // eslint-disable-next-line max-len + 'Expected the element to match the following style:\n{\n "border": "1px solid black",\n "color": "rgb(255, 0, 0)",\n "display": "flex"\n}', + ); + + expect(test.not.toHaveStyle({ border: "1px solid black", color: "red", display: "flex" })).toBeEqual(test); }); - }); }); + context("when the element partially match the style", () => { + it("throws an assertion error", () => { + const { getByTestId } = render( +
, + ); - context("when the style is passed as an object", () => { - context("and the element has the expected style", () => { - it("returns the assertion instance", () => { - const { getByTestId } = render(
); const divTest = getByTestId("test-div"); const test = new ElementAssertion(divTest); - - expect(test.toHaveStyle({color: "red", display: "flex", border: "1px solid black"})).toBeEqual(test); - expect(() => test.not.toHaveStyle({color: "red", display: "flex", border: "1px solid black"})) + expect(() => test.toHaveStyle(({ color: "red", display: "flex" }))) .toThrowError(AssertionError) - .toHaveMessage('Expected the element to NOT have {"color":"rgb(255, 0, 0)","display":"flex","border":"1px solid black"} style'); - - }); - }); + .toHaveMessage( + // eslint-disable-next-line max-len + 'Expected the element to match the following style:\n{\n "color": "rgb(255, 0, 0)",\n "display": "flex"\n}', + ); + + expect(test.not.toHaveStyle({ border: "1px solid black", color: "red", display: "flex" })).toBeEqual(test); - context("and the element does not have the expected style", () => { - it("throws an assertion error", () => { - const { getByTestId } = render(
); - const divTest = getByTestId("test-div"); - const test = new ElementAssertion(divTest); - - expect(() => test.toHaveStyle(({ color: "red", display: "flex", border: "1px solid black" }))) - .toThrowError(AssertionError) - .toHaveMessage("Expected the element to have {\"color\":\"rgb(255, 0, 0)\",\"display\":\"flex\",\"border\":\"1px solid black\"} style"); - - }); }); - }); + }); }); -}) +}); From 9e88d393fa15fa1cfbf572a32a6c2baab514d155 Mon Sep 17 00:00:00 2001 From: Sebas Cruz Date: Mon, 6 Oct 2025 15:48:16 -0500 Subject: [PATCH 12/17] 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 c4fc8a62..34f7704e 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 f91f10cd..c2263372 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 13/17] 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 34f7704e..055511e6 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 843be7d9..592442c7 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 14/17] 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 c2263372..d4f4dc29 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 15/17] 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 d4f4dc29..3719659d 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 16/17] 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 055511e6..db81dd6c 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 592442c7..197cfd0c 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 17/17] 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 db81dd6c..e89aa48d 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({