Skip to content

Commit

Permalink
queryAllByAttribute (#18)
Browse files Browse the repository at this point in the history
* Add queryBy

* Fix text routing

* Query by attribute

* Export query helpers
  • Loading branch information
hmallen99 authored May 8, 2024
1 parent a8b1bb2 commit 0a4d3d5
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 34 deletions.
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './queries';
export * from './event';
export * from './query-helpers';
6 changes: 3 additions & 3 deletions src/queries/text.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
findByText,
getAllByText,
getByText,
getMultipleElementsFoundError,
queryAllByText,
queryByText,
} from './text';
Expand All @@ -16,6 +15,7 @@ import {
} from '@babylonjs/gui';
import { BabylonContainer } from './utils';
import { getElementError } from '@testing-library/dom';
import { getMultipleElementsFoundError } from '../query-helpers';

describe('text query', () => {
let scene: Scene,
Expand Down Expand Up @@ -232,7 +232,7 @@ describe('text query', () => {

expect(() => singleText(container, 'Hello World!')).toThrow(
new Error(
`Found multiple elements matching Hello World!\n\n(If this is intentional, then use the \`*AllBy*\` variant of the query (like \`queryAllByText\`, \`getAllByText\`, or \`findAllByText\`)). Container: ${container}`
`Found multiple elements with the text: Hello World!\n\n(If this is intentional, then use the \`*AllBy*\` variant of the query (like \`queryAllByText\`, \`getAllByText\`, or \`findAllByText\`)). Container: ${container}`
)
);
}
Expand Down Expand Up @@ -276,7 +276,7 @@ describe('text query', () => {
).rejects.toEqual(
getElementError(
getMultipleElementsFoundError(
'Found multiple elements matching Hello World!',
'Found multiple elements with the text: Hello World!',
container
).message,
document.firstElementChild as HTMLElement
Expand Down
28 changes: 7 additions & 21 deletions src/queries/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,13 @@ import { Control, TextBlock } from '@babylonjs/gui';
import { BabylonContainer, findAllMatchingDescendants } from './utils';
import { buildQueries } from '../query-helpers';

export function getMultipleElementsFoundError(
message: string,
container: BabylonContainer
) {
return new Error(
`${message}\n\n(If this is intentional, then use the \`*AllBy*\` variant of the query (like \`queryAllByText\`, \`getAllByText\`, or \`findAllByText\`)). Container: ${container}`
);
}
const getMultipleError = (_c: BabylonContainer, text: string) =>
`Found multiple elements with the text: ${text}`;

const getMissingError = (message: string, container: BabylonContainer) => {
return new Error(`${message}. Container: ${container}`);
return new Error(
`Failed to find an element matching: ${message}. Container: ${container}`
);
};

const queryAllByText = (
Expand All @@ -23,13 +19,7 @@ const queryAllByText = (
return control instanceof TextBlock && control.text === text;
};

const baseArray: Control[] = [];

if (container instanceof Control && matcher(container)) {
baseArray.push(container);
}

return [...baseArray, ...findAllMatchingDescendants(container, matcher)];
return findAllMatchingDescendants(container, matcher);
};

const {
Expand All @@ -38,11 +28,7 @@ const {
getBy: getByText,
findAllBy: findAllByText,
findBy: findByText,
} = buildQueries(
queryAllByText,
getMultipleElementsFoundError,
getMissingError
);
} = buildQueries(queryAllByText, getMultipleError, getMissingError);

export {
queryAllByText,
Expand Down
10 changes: 9 additions & 1 deletion src/queries/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,15 @@ const findAllMatchingInControl = (
container: Control,
matcher: (control: Control) => boolean
): Control[] => {
return container.getDescendants().filter((control) => matcher(control));
const controls = [];
if (matcher(container)) {
controls.push(container);
}

return [
...controls,
...container.getDescendants().filter((control) => matcher(control)),
];
};
const findAllMatchingInTexture = (
container: AdvancedDynamicTexture,
Expand Down
115 changes: 115 additions & 0 deletions src/query-helpers.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { Engine, Mesh, MeshBuilder, NullEngine, Scene } from '@babylonjs/core';

import {
AdvancedDynamicTexture,
Control,
Grid,
TextBlock,
} from '@babylonjs/gui';
import { BabylonContainer } from './queries/utils';
import {
getMultipleElementsFoundError,
queryAllByAttribute,
queryByAttribute,
} from './query-helpers';

describe('query-helpers', () => {
let scene: Scene,
engine: Engine,
texture: AdvancedDynamicTexture,
containerControl: Grid,
expectedControl: Control,
uiPlane: Mesh;

beforeAll(() => {
engine = new NullEngine();
});

beforeEach(() => {
scene = new Scene(engine);

uiPlane = MeshBuilder.CreatePlane('container');
texture = AdvancedDynamicTexture.CreateForMesh(uiPlane);

containerControl = new Grid('container');
containerControl.addColumnDefinition(1);
containerControl.addColumnDefinition(1);
texture.addControl(containerControl);

expectedControl = new TextBlock('text', 'Hello World!');
containerControl.addControl(expectedControl, 0, 0);
});

afterEach(() => {
texture.dispose();
uiPlane.material?.dispose();
uiPlane.dispose();
scene.dispose();
});

afterAll(() => {
engine.dispose();
});

const scenarios: [string, () => BabylonContainer][] = [
['a scene', () => scene],
['a texture', () => texture],
['a control', () => containerControl],
];

describe.each(scenarios)(
'query*ByAttribute within %s',
(_label, getContainer) => {
it('should queryAllByAttribute', () => {
const container = getContainer();

const controls = queryAllByAttribute(
'width',
container,
'100%'
);

expect(controls).toHaveLength(2);
expect(controls).toEqual([containerControl, expectedControl]);
});

it('should error when queryByAttribute finds multiple elements', () => {
const container = getContainer();

expect(() => {
queryByAttribute('width', container, '100%');
}).toThrow(
getMultipleElementsFoundError(
'Found multiple elements by [width=100%]',
container
)
);
});
}
);

describe('query*ByAttribute within itself', () => {
it('should queryAllByAttribute', () => {
const controls = queryAllByAttribute(
'width',
expectedControl,
'100%'
);

expect(controls).toHaveLength(1);
expect(controls).toEqual([expectedControl]);
});

it('should return a single element', () => {
const control = queryByAttribute('width', expectedControl, '100%');

expect(control).toEqual(expectedControl);
});

it('should return null if no element is found', () => {
const control = queryByAttribute('width', expectedControl, '99%%');

expect(control).toEqual(null);
});
});
});
57 changes: 48 additions & 9 deletions src/query-helpers.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,53 @@
import { waitFor, waitForOptions } from '@testing-library/dom';
import { BabylonContainer, findAllMatchingDescendants } from './queries/utils';

export function getMultipleElementsFoundError<ContainerType>(
message: string,
container: ContainerType
) {
return new Error(
`${message}\n\n(If this is intentional, then use the \`*AllBy*\` variant of the query (like \`queryAllByText\`, \`getAllByText\`, or \`findAllByText\`)). Container: ${container}`
);
}

export function queryAllByAttribute<AttributeType>(
attribute: string,
container: BabylonContainer,
value: AttributeType
) {
return findAllMatchingDescendants(
container,
(control) => control[attribute] === value
);
}

export function queryByAttribute<AttributeType>(
attribute: string,
container: BabylonContainer,
value: AttributeType
) {
const controls = queryAllByAttribute(attribute, container, value);

if (controls.length === 0) {
return null;
}

if (controls.length > 1) {
throw getMultipleElementsFoundError(
`Found multiple elements by [${attribute}=${value}]`,
container
);
}

return controls[0];
}

export function buildQueries<ContainerType, MatcherType, ResultType>(
queryAllBy: (
container: ContainerType,
matcher: MatcherType
) => ResultType[],
getMultipleError: (message: string, container: ContainerType) => Error,
getMultipleError: (container: ContainerType, message: string) => string,
getMissingError: (text: string, container: ContainerType) => Error
) {
const queryBy = (container: ContainerType, matcher: MatcherType) => {
Expand All @@ -16,8 +58,8 @@ export function buildQueries<ContainerType, MatcherType, ResultType>(
}

if (result.length > 1) {
throw getMultipleError(
`Found multiple elements matching ${matcher}`,
throw getMultipleElementsFoundError(
getMultipleError(container, `${matcher}`),
container
);
}
Expand All @@ -28,19 +70,16 @@ export function buildQueries<ContainerType, MatcherType, ResultType>(
const getAllBy = (container: ContainerType, matcher: MatcherType) => {
const result = queryAllBy(container, matcher);
if (result.length === 0) {
throw getMissingError(
`Failed to find an element matching: ${matcher}`,
container
);
throw getMissingError(`${matcher}`, container);
}
return result;
};

const getBy = (container: ContainerType, matcher: MatcherType) => {
const result = getAllBy(container, matcher);
if (result.length > 1) {
throw getMultipleError(
`Found multiple elements matching ${matcher}`,
throw getMultipleElementsFoundError(
getMultipleError(container, `${matcher}`),
container
);
}
Expand Down

0 comments on commit 0a4d3d5

Please sign in to comment.