Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adds findAllComponents selector #74

Merged
merged 1 commit into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 43 additions & 3 deletions src/core/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
/*eslint-env browser*/
import { IElementWrapper } from './interfaces';
import { KeyCode, isScopedSelector, substituteScope } from './utils';
import { KeyCode, isScopedSelector, substituteScope, appendSelector } from './utils';
import { act } from './utils-dom';

// Original KeyboardEventInit lacks some properties https://github.com/Microsoft/TypeScript/issues/15228
Expand All @@ -23,6 +23,14 @@ const defaultParams = {
cancelable: true,
};

interface WrapperClass<Wrapper, ElementType> {
new (element: ElementType): Wrapper;
}

interface ComponentWrapperClass<Wrapper, ElementType> extends WrapperClass<Wrapper, ElementType> {
rootSelector: string;
}

export class AbstractWrapper<ElementType extends Element>
implements IElementWrapper<ElementType, Array<ElementWrapper<ElementType>>>
{
Expand Down Expand Up @@ -120,13 +128,45 @@ export class AbstractWrapper<ElementType extends Element>
return this.findAll<NewElementType>(`.${className}`);
}

findComponent<Wrapper extends ComponentWrapper<ElementType>, ElementType extends HTMLElement>(
/**
* Returns the component wrapper matching the specified selector.
* If the specified selector doesn't match any element, it returns `null`.
*
* Note: This function returns the specified component's wrapper even if the specified selector points to a different component type.
*
* @param {string} selector CSS selector
* @param {WrapperClass} ComponentClass Component's wrapper class
* @returns `Wrapper | null`
*/
findComponent<Wrapper extends ComponentWrapper, ElementType extends HTMLElement>(
selector: string,
ComponentClass: { new (element: ElementType): Wrapper }
ComponentClass: WrapperClass<Wrapper, ElementType>
): Wrapper | null {
const elementWrapper = this.find<ElementType>(selector);
return elementWrapper ? new ComponentClass(elementWrapper.getElement()) : null;
}

orangevolon marked this conversation as resolved.
Show resolved Hide resolved
/**
* Returns the wrappers of all components that match the specified component type and the specified CSS selector.
* If no CSS selector is specified, returns all of the components that match the specified component type.
* If no matching component is found, returns an empty array.
*
* @param {ComponentWrapperClass} ComponentClass Component's wrapper class
* @param {string} [selector] CSS selector
* @returns `Array<Wrapper>`
*/
findAllComponents<Wrapper extends ComponentWrapper, ElementType extends HTMLElement>(
ComponentClass: ComponentWrapperClass<Wrapper, ElementType>,
selector?: string
): Array<Wrapper> {
const componentRootSelector = `.${ComponentClass.rootSelector}`;
const componentCombinedSelector = selector
? appendSelector(componentRootSelector, selector)
: componentRootSelector;

const elementWrappers = this.findAll<ElementType>(componentCombinedSelector);
return elementWrappers.map(wrapper => new ComponentClass(wrapper.getElement()));
}
orangevolon marked this conversation as resolved.
Show resolved Hide resolved
}

export class ElementWrapper<ElementType extends Element = HTMLElement> extends AbstractWrapper<ElementType> {}
Expand Down
41 changes: 37 additions & 4 deletions src/core/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ const getRootSelector = (selector: string, root: string): string => {
return getUnscopedClassName(rootSelector);
};

interface WrapperClass<Wrapper> {
new (selector: string): Wrapper;
}

interface ComponentWrapperClass<Wrapper> extends WrapperClass<Wrapper> {
rootSelector: string;
}

export class AbstractWrapper implements IElementWrapper<string, MultiElementWrapper<ElementWrapper>> {
constructor(protected root: string) {}

Expand Down Expand Up @@ -35,13 +43,38 @@ export class AbstractWrapper implements IElementWrapper<string, MultiElementWrap
return this.findAll(`.${className}`);
}

findComponent<Wrapper extends ComponentWrapper>(
selector: string,
ComponentClass: { new (element: string): Wrapper }
): Wrapper {
/**
* Returns a wrapper that matches the specified component type with the specified CSS selector.
*
* Note: This function returns the specified component's wrapper even if the specified selector points to a different component type.
*
* @param {string} selector CSS selector
* @param {WrapperClass} ComponentClass Component's wrapper class
* @returns `Wrapper`
*/
findComponent<Wrapper extends ComponentWrapper>(selector: string, ComponentClass: WrapperClass<Wrapper>): Wrapper {
return new ComponentClass(this.find(selector).getElement());
}

/**
* Returns a multi-element wrapper that matches the specified component type with the specified CSS selector.
* If no CSS selector is specified, returns a multi-element wrapper that matches the specified component type.
*
* @param {string} [selector] CSS Selector
* @returns {MultiElementWrapper}
*/
findAllComponents<Wrapper extends ComponentWrapper>(
ComponentClass: ComponentWrapperClass<Wrapper>,
selector?: string
): MultiElementWrapper<Wrapper> {
const componentRootSelector = `.${ComponentClass.rootSelector}`;
const componentCombinedSelector = selector
? appendSelector(componentRootSelector, selector)
: componentRootSelector;
const rootSelector = getRootSelector(componentCombinedSelector, this.root);
return new MultiElementWrapper(rootSelector, selector => new ComponentClass(selector));
}

toSelector(): string {
return this.root;
}
Expand Down
Loading
Loading