Skip to content

Commit

Permalink
feat: [#1159] Adds support for the :disabled pseudo-class in CSS and …
Browse files Browse the repository at this point in the history
…query selectors (#1589)

* feat: [#1159] Adds support for the :disabled pseudo-class in CSS and query selectors

* chore: [#1159] Uses the correct name in comments

* chore: [#1159] Uses the correct name in comments
  • Loading branch information
capricorn86 authored Nov 7, 2024
1 parent cecb787 commit 3a79654
Show file tree
Hide file tree
Showing 15 changed files with 61 additions and 17 deletions.
2 changes: 1 addition & 1 deletion packages/happy-dom/src/css/CSSStyleSheet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import * as PropertySymbol from '../PropertySymbol.js';
* https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet.
*/
export default class CSSStyleSheet {
// Injected by WindowClassExtender
// Injected by WindowContextClassExtender
protected declare [PropertySymbol.window]: BrowserWindow;

public value: string = null;
Expand Down
2 changes: 1 addition & 1 deletion packages/happy-dom/src/dom-parser/DOMParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import NodeTypeEnum from '../nodes/node/NodeTypeEnum.js';
* https://developer.mozilla.org/en-US/docs/Web/API/DOMParser.
*/
export default class DOMParser {
// Injected by WindowClassExtender
// Injected by WindowContextClassExtender
protected declare [PropertySymbol.window]: BrowserWindow;

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/happy-dom/src/event/EventTarget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import BrowserWindow from '../window/BrowserWindow.js';
* Handles events.
*/
export default class EventTarget {
// Injected by WindowClassExtender
// Injected by WindowContextClassExtender
protected declare [PropertySymbol.window]: BrowserWindow;

public readonly [PropertySymbol.listeners]: {
Expand Down
2 changes: 1 addition & 1 deletion packages/happy-dom/src/fetch/AbortController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import BrowserWindow from '../window/BrowserWindow.js';
* @see https://developer.mozilla.org/en-US/docs/Web/API/AbortController
*/
export default class AbortController {
// Injected by WindowClassExtender
// Injected by WindowContextClassExtender
protected declare [PropertySymbol.window]: BrowserWindow;

// Public properties
Expand Down
2 changes: 1 addition & 1 deletion packages/happy-dom/src/fetch/AbortSignal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import BrowserWindow from '../window/BrowserWindow.js';
* @see https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal
*/
export default class AbortSignal extends EventTarget {
// Injected by WindowClassExtender
// Injected by WindowContextClassExtender
protected declare static [PropertySymbol.window]: BrowserWindow;
protected declare [PropertySymbol.window]: BrowserWindow;

Expand Down
2 changes: 1 addition & 1 deletion packages/happy-dom/src/fetch/Request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import WindowBrowserContext from '../window/WindowBrowserContext.js';
* @see https://fetch.spec.whatwg.org/#request-class
*/
export default class Request implements Request {
// Injected by WindowClassExtender
// Injected by WindowContextClassExtender
protected declare [PropertySymbol.window]: BrowserWindow;

// Public properties
Expand Down
2 changes: 1 addition & 1 deletion packages/happy-dom/src/fetch/Response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const REDIRECT_STATUS_CODES = [301, 302, 303, 307, 308];
* @see https://developer.mozilla.org/en-US/docs/Web/API/Response/Response
*/
export default class Response implements Response {
// Injected by WindowClassExtender
// Injected by WindowContextClassExtender
protected declare static [PropertySymbol.window]: BrowserWindow;
protected declare [PropertySymbol.window]: BrowserWindow;

Expand Down
2 changes: 1 addition & 1 deletion packages/happy-dom/src/form-data/FormData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type FormDataEntry = {
* @see https://developer.mozilla.org/en-US/docs/Web/API/FormData
*/
export default class FormData implements Iterable<[string, string | File]> {
// Injected by WindowClassExtender
// Injected by WindowContextClassExtender
protected declare [PropertySymbol.window]: BrowserWindow;

#entries: FormDataEntry[] = [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import MutationRecord from './MutationRecord.js';
* @see https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
*/
export default class MutationObserver {
// Injected by WindowClassExtender
// Injected by WindowContextClassExtender
protected declare [PropertySymbol.window]: BrowserWindow;
#callback: (records: MutationRecord[], observer: MutationObserver) => void;
#listeners: MutationObserverListener[] = [];
Expand Down
2 changes: 1 addition & 1 deletion packages/happy-dom/src/nodes/node/Node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export default class Node extends EventTarget {
constructor() {
super();

// Window injected by WindowClassExtender (used when the Node can be created using "new" keyword)
// Window injected by WindowContextClassExtender (used when the Node can be created using "new" keyword)
if (this[PropertySymbol.window]) {
this[PropertySymbol.ownerDocument] = this[PropertySymbol.window].document;
} else {
Expand Down
5 changes: 5 additions & 0 deletions packages/happy-dom/src/query-selector/SelectorItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,11 @@ export default class SelectorItem {
return element[PropertySymbol.tagName] === 'INPUT' && (<HTMLInputElement>element).checked
? { priorityWeight: 10 }
: null;
case 'disabled':
return 'disabled' in element &&
element[PropertySymbol.attributes][PropertySymbol.namedItems].has('disabled')
? { priorityWeight: 10 }
: null;
case 'empty':
return !(<Element>element)[PropertySymbol.elementArray].length
? { priorityWeight: 10 }
Expand Down
2 changes: 1 addition & 1 deletion packages/happy-dom/src/range/Range.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import BrowserWindow from '../window/BrowserWindow.js';
* https://developer.mozilla.org/en-US/docs/Web/API/Range.
*/
export default class Range {
// Injected by WindowClassExtender
// Injected by WindowContextClassExtender
protected declare [PropertySymbol.window]: BrowserWindow;

public static readonly END_TO_END: number = RangeHowEnum.endToEnd;
Expand Down
8 changes: 4 additions & 4 deletions packages/happy-dom/src/window/BrowserWindow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ import {
} from 'node:perf_hooks';
import EventPhaseEnum from '../event/EventPhaseEnum.js';
import HTMLOptionsCollection from '../nodes/html-select-element/HTMLOptionsCollection.js';
import WindowClassExtender from './WindowClassExtender.js';
import WindowContextClassExtender from './WindowContextClassExtender.js';
import WindowBrowserContext from './WindowBrowserContext.js';
import CanvasCaptureMediaStreamTrack from '../nodes/html-canvas-element/CanvasCaptureMediaStreamTrack.js';
import SVGSVGElement from '../nodes/svg-svg-element/SVGSVGElement.js';
Expand Down Expand Up @@ -358,7 +358,7 @@ export default class BrowserWindow extends EventTarget implements INodeJSGlobal
public readonly CharacterData = CharacterData;
public readonly DocumentType = DocumentType;

// Nodes that can be created using "new" keyword (populated by WindowClassExtender)
// Nodes that can be created using "new" keyword (populated by WindowContextClassExtender)
public declare readonly Document: typeof Document;
public declare readonly HTMLDocument: typeof HTMLDocument;
public declare readonly XMLDocument: typeof XMLDocument;
Expand Down Expand Up @@ -571,7 +571,7 @@ export default class BrowserWindow extends EventTarget implements INodeJSGlobal
public readonly WebGLContextEvent = Event;
public readonly TextEvent = Event;

// Other classes that has to be bound to the Window context (populated by WindowClassExtender)
// Other classes that has to be bound to the Window context (populated by WindowContextClassExtender)
public declare readonly NodeIterator: typeof NodeIterator;
public declare readonly TreeWalker: typeof TreeWalker;
public declare readonly MutationObserver: typeof MutationObserver;
Expand Down Expand Up @@ -825,7 +825,7 @@ export default class BrowserWindow extends EventTarget implements INodeJSGlobal

this[PropertySymbol.setupVMContext]();

WindowClassExtender.extendClass(this);
WindowContextClassExtender.extendClasses(this);

// Document
this.document = new this.HTMLDocument();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,13 @@ import NamedNodeMapImplementation from '../nodes/element/NamedNodeMap.js';
*
* By using WindowBrowserContext, the classes can get access to their Browser context, for accessing settings or navigating the browser.
*/
export default class WindowClassExtender {
export default class WindowContextClassExtender {
/**
* Extends classes with a "window" property.
*
* @param window Window.
*/
public static extendClass(window: BrowserWindow): void {
public static extendClasses(window: BrowserWindow): void {
/* eslint-disable jsdoc/require-jsdoc */

// Document
Expand Down
39 changes: 39 additions & 0 deletions packages/happy-dom/test/query-selector/QuerySelector.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,45 @@ describe('QuerySelector', () => {
expect((<HTMLInputElement>elements[0]).value).toBe('two');
});

it('Returns all elements matching ":disabled".', () => {
const container = document.createElement('div');
container.innerHTML = `
<form>
<input type="radio" id="id1" name="op" value="one" disabled/>
<input type="radio" id="id2" name="op" value="two"/>
<button disabled>Disabled</button>
<fieldset disabled></fieldset>
<select disabled>
<option>Option 1</option>
<option disabled>Option 2</option>
</select>
<textarea disabled></textarea>
</form>
`;
const elements = container.querySelectorAll(':disabled');

expect(elements.length).toBe(6);
expect(elements[0] === container.children[0].children[0]).toBe(true);
expect(elements[1] === container.children[0].children[2]).toBe(true);
expect(elements[2] === container.children[0].children[3]).toBe(true);
expect(elements[3] === container.children[0].children[4]).toBe(true);
expect(elements[4] === container.children[0].children[4].children[1]).toBe(true);
expect(elements[5] === container.children[0].children[5]).toBe(true);

// Here we also tests that cache works

(<HTMLInputElement>container.children[0].children[0]).disabled = false;

const elements2 = container.querySelectorAll(':disabled');

expect(elements2.length).toBe(5);
expect(elements2[0] === container.children[0].children[2]).toBe(true);
expect(elements2[1] === container.children[0].children[3]).toBe(true);
expect(elements2[2] === container.children[0].children[4]).toBe(true);
expect(elements2[3] === container.children[0].children[4].children[1]).toBe(true);
expect(elements2[4] === container.children[0].children[5]).toBe(true);
});

it('Returns all elements matching "span:not([type=hidden])".', () => {
const container = document.createElement('div');
container.innerHTML = QuerySelectorHTML;
Expand Down

0 comments on commit 3a79654

Please sign in to comment.