Skip to content

Commit 2f3d0b5

Browse files
committed
Add new matches based on the class input
1 parent 44c101e commit 2f3d0b5

File tree

2 files changed

+127
-46
lines changed

2 files changed

+127
-46
lines changed

packages/dom/src/lib/ElementAssertion.ts

+76-29
Original file line numberDiff line numberDiff line change
@@ -91,45 +91,92 @@ export class ElementAssertion<T extends Element> extends Assertion<T> {
9191
});
9292
}
9393

94-
/**
95-
* Check if the element has a specific class or classes.
96-
*
97-
* Validates that the provided element contains specified classes.
98-
* Allows checking for one or more class names and supports exact matching.
99-
*
100-
* @param classNames - A single class name or an array of class names to check.
101-
* @param options - Optional settings for matching:
102-
* - `exact` (boolean): When true, checks for an exact match of all classes.
103-
* @returns the assertion instance.
104-
*/
105-
public toHaveClass(classNames: string | string[], options: { exact?: boolean; } = {}): this {
106-
const actualClassList = this.actual.className.split(/\s+/).filter(Boolean);
107-
const expectedClassList = Array.isArray(classNames) ? classNames : [classNames];
108-
const { exact = false } = options;
94+
/**
95+
* Asserts that the element has the specified class.
96+
*
97+
* @param className - The class name to check.
98+
* @returns the assertion instance.
99+
*/
100+
public toHaveClass(className: string): this {
101+
const actualClassList = this.getClassList();
102+
103+
return this.assertClassPresence(
104+
actualClassList.includes(className),
105+
[className],
106+
`Expected the element to have class: "${className}"`,
107+
`Expected the element to NOT have class: "${className}"`,
108+
);
109+
}
110+
111+
/**
112+
* Asserts that the element has at least one of the specified classes.
113+
*
114+
* @param classNames - A variadic list of class names to check.
115+
* @returns the assertion instance.
116+
*/
117+
public toHaveAnyClass(...classNames: string[]): this {
118+
const actualClassList = this.getClassList();
119+
120+
return this.assertClassPresence(
121+
classNames.some(cls => actualClassList.includes(cls)),
122+
classNames,
123+
`Expected the element to have at least one of these classes: "${classNames.join(" ")}"`,
124+
`Expected the element to NOT have any of these classes: "${classNames.join(" ")}"`,
125+
);
126+
}
127+
128+
/**
129+
* Asserts that the element has all of the specified classes.
130+
*
131+
* @param classNames - A variadic list of class names to check.
132+
* @returns the assertion instance.
133+
*/
134+
public toHaveAllClasses(...classNames: string[]): this {
135+
const actualClassList = this.getClassList();
136+
137+
return this.assertClassPresence(
138+
classNames.every(cls => actualClassList.includes(cls)),
139+
classNames,
140+
`Expected the element to have all of these classes: "${classNames.join(" ")}"`,
141+
`Expected the element to NOT have all of these classes: "${classNames.join(" ")}"`,
142+
);
143+
}
144+
145+
private getClassList(): string[] {
146+
return this.actual.className.split(/\s+/).filter(Boolean);
147+
}
148+
149+
/**
150+
* Helper method to assert the presence or absence of class names.
151+
*
152+
* @param assertCondition - Boolean to determine assertion pass or fail.
153+
* @param classNames - Array of class names involved in the assertion.
154+
* @param message - Assertion error message.
155+
* @param invertedMessage - Inverted assertion error message.
156+
* @returns the assertion instance.
157+
*/
158+
private assertClassPresence(
159+
assertCondition: boolean,
160+
classNames: string[],
161+
message: string,
162+
invertedMessage: string,
163+
): this {
164+
const actualClassList = this.getClassList();
109165

110166
const error = new AssertionError({
111167
actual: actualClassList,
112-
expected: expectedClassList,
113-
message: exact
114-
? `Expected the element to have exactly these classes: "${expectedClassList.join(" ")}"`
115-
: `Expected the element to have class(es): "${expectedClassList.join(" ")}"`,
168+
expected: classNames,
169+
message,
116170
});
117171

118172
const invertedError = new AssertionError({
119173
actual: actualClassList,
120-
expected: expectedClassList,
121-
message: exact
122-
? `Expected the element to NOT have exactly these classes: "${expectedClassList.join(" ")}"`
123-
: `Expected the element to NOT have class(es): "${expectedClassList.join(" ")}"`,
174+
expected: classNames,
175+
message: invertedMessage,
124176
});
125177

126-
const assertWhen = exact
127-
? actualClassList.length === expectedClassList.length
128-
&& expectedClassList.every(cls => actualClassList.includes(cls))
129-
: expectedClassList.every(cls => actualClassList.includes(cls));
130-
131178
return this.execute({
132-
assertWhen,
179+
assertWhen: assertCondition,
133180
error,
134181
invertedError,
135182
});

packages/dom/test/unit/lib/ElementAssertion.test.tsx

+51-17
Original file line numberDiff line numberDiff line change
@@ -175,63 +175,97 @@ describe("[Unit] ElementAssertion.test.ts", () => {
175175
});
176176

177177
describe(".toHaveClass", () => {
178-
context("when the element has the the expected class", () => {
178+
context("when the element has the expected class", () => {
179179
it("returns the assertion instance", async () => {
180180
const { findByTestId } = render(<HaveClassTestComponent />);
181181
const divTest = await findByTestId("classTest");
182-
divTest.className = "foo bar";
182+
divTest.classList.add("foo", "bar");
183183
const test = new ElementAssertion(divTest);
184184

185185
expect(test.toHaveClass("foo")).toBeEqual(test);
186186

187187
expect(() => test.not.toHaveClass("foo"))
188188
.toThrowError(AssertionError)
189-
.toHaveMessage("Expected the element to NOT have class(es): \"foo\"");
189+
.toHaveMessage('Expected the element to NOT have class: "foo"');
190190
});
191191
});
192192

193-
context("when the element does not have the expected class ", () => {
193+
context("when the element does not have the expected class", () => {
194+
it("throws an assertion error", async () => {
195+
const { findByTestId } = render(<HaveClassTestComponent />);
196+
const divTest = await findByTestId("classTest");
197+
divTest.classList.add("foo", "bar");
198+
const test = new ElementAssertion(divTest);
199+
200+
expect(() => test.toHaveClass("baz"))
201+
.toThrowError(AssertionError)
202+
.toHaveMessage('Expected the element to have class: "baz"');
203+
204+
expect(test.not.toHaveClass("baz")).toBeEqual(test);
205+
});
206+
});
207+
});
208+
209+
describe(".toHaveAnyClass", () => {
210+
context("when the element has at least one of the expected classes", () => {
211+
it("returns the assertion instance", async () => {
212+
const { findByTestId } = render(<HaveClassTestComponent />);
213+
const divTest = await findByTestId("classTest");
214+
divTest.classList.add("foo", "bar");
215+
const test = new ElementAssertion(divTest);
216+
217+
expect(test.toHaveAnyClass("bar", "baz")).toBeEqual(test);
218+
219+
expect(() => test.not.toHaveAnyClass("bar", "baz"))
220+
.toThrowError(AssertionError)
221+
.toHaveMessage('Expected the element to NOT have any of these classes: "bar baz"');
222+
});
223+
});
224+
225+
context("when the element does not have any of the expected classes", () => {
194226
it("throws an assertion error", async () => {
195227
const { findByTestId } = render(<HaveClassTestComponent />);
196228
const divTest = await findByTestId("classTest");
197229
divTest.className = "foo";
198230
const test = new ElementAssertion(divTest);
199231

200-
expect(() => test.toHaveClass("bar"))
232+
expect(() => test.toHaveAnyClass("bar", "baz"))
201233
.toThrowError(AssertionError)
202-
.toHaveMessage("Expected the element to have class(es): \"bar\"");
234+
.toHaveMessage('Expected the element to have at least one of these classes: "bar baz"');
203235

204-
expect(test.not.toHaveClass("bar")).toBeEqual(test);
236+
expect(test.not.toHaveAnyClass("bar", "baz")).toBeEqual(test);
205237
});
206238
});
239+
});
207240

208-
context("when the element element has the the exact matching expected class", () => {
241+
describe(".toHaveAllClasses", () => {
242+
context("when the element has all the expected classes", () => {
209243
it("returns the assertion instance", async () => {
210244
const { findByTestId } = render(<HaveClassTestComponent />);
211245
const divTest = await findByTestId("classTest");
212-
divTest.className = "foo bar";
246+
divTest.classList.add("foo", "bar", "baz");
213247
const test = new ElementAssertion(divTest);
214248

215-
expect(test.toHaveClass(["foo", "bar"], { exact: true })).toBeEqual(test);
249+
expect(test.toHaveAllClasses("foo", "bar")).toBeEqual(test);
216250

217-
expect(() => test.not.toHaveClass(["foo", "bar"], { exact: true }))
251+
expect(() => test.not.toHaveAllClasses("foo", "bar"))
218252
.toThrowError(AssertionError)
219-
.toHaveMessage("Expected the element to NOT have exactly these classes: \"foo bar\"");
253+
.toHaveMessage('Expected the element to NOT have all of these classes: "foo bar"');
220254
});
221255
});
222256

223-
context("when the element does not have the exact matching expected class ", () => {
257+
context("when the element does not have all the expected classes", () => {
224258
it("throws an assertion error", async () => {
225259
const { findByTestId } = render(<HaveClassTestComponent />);
226260
const divTest = await findByTestId("classTest");
227-
divTest.className = "foo bar extra";
261+
divTest.classList.add("foo", "bar");
228262
const test = new ElementAssertion(divTest);
229263

230-
expect(() => test.toHaveClass(["foo", "bar"], { exact: true }))
264+
expect(() => test.toHaveAllClasses("foo", "bar", "baz"))
231265
.toThrowError(AssertionError)
232-
.toHaveMessage("Expected the element to have exactly these classes: \"foo bar\"");
266+
.toHaveMessage('Expected the element to have all of these classes: "foo bar baz"');
233267

234-
expect(test.not.toHaveClass(["foo", "bar"], { exact: true })).toBeEqual(test);
268+
expect(test.not.toHaveAllClasses("foo", "bar", "baz")).toBeEqual(test);
235269
});
236270
});
237271
});

0 commit comments

Comments
 (0)