Skip to content

Commit

Permalink
Fix test-utils typescript errors (#8483)
Browse files Browse the repository at this point in the history
  • Loading branch information
Pessimistress committed Feb 9, 2024
1 parent df725d5 commit 2737b0a
Show file tree
Hide file tree
Showing 9 changed files with 478 additions and 171 deletions.
69 changes: 54 additions & 15 deletions modules/test-utils/src/generate-layer-tests.ts
Expand Up @@ -17,39 +17,60 @@
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import {_count as count} from '@deck.gl/core';
import {_count as count, Layer} from '@deck.gl/core';

import type {DefaultProps} from '@deck.gl/core';
import type {LayerTestCase, LayerClass} from './lifecycle-test';

// eslint-disable-next-line @typescript-eslint/no-empty-function
function noop() {}

function defaultAssert(condition, comment) {
function defaultAssert(condition: any, comment: string): void {
if (!condition) {
throw new Error(comment);
}
}

// Automatically generate testLayer test cases
export function generateLayerTests({
export function generateLayerTests<LayerT extends Layer>({
Layer,
sampleProps = {},
assert = defaultAssert,
onBeforeUpdate = noop,
onAfterUpdate = noop,
runDefaultAsserts = true
}) {
}: {
Layer: LayerClass<LayerT>;
/**
* Override default props during the test
*/
sampleProps?: Partial<LayerT['props']>;
assert?: (condition: any, comment: string) => void;
onBeforeUpdate: LayerTestCase<LayerT>['onBeforeUpdate'];
onAfterUpdate: LayerTestCase<LayerT>['onAfterUpdate'];

/**
* Test some typical assumptions after layer updates
* For primitive layers, assert that layer has model(s).
* For composite layers, assert that layer has sublayer(s).
* @default true
*/
runDefaultAsserts?: boolean;
}): LayerTestCase<LayerT>[] {
assert(Layer.layerName, 'Layer should have display name');

function wrapTestCaseTitle(title) {
function wrapTestCaseTitle(title: string): string {
return `${Layer.layerName}#${title}`;
}

const testCases = [
const testCases: LayerTestCase<LayerT>[] = [
{
title: 'Empty props',
props: {}
},
{
title: 'Null data',
// @ts-expect-error null may not be an expected data type
updateProps: {data: null}
},
{
Expand All @@ -62,14 +83,15 @@ export function generateLayerTests({
// Calling constructor for the first time resolves default props
// eslint-disable-next-line
new Layer({});
} catch (error) {
assert(false, `Construct ${Layer.layerName} throws: ${error.message}`);
} catch (error: unknown) {
assert(false, `Construct ${Layer.layerName} throws: ${(error as Error).message}`);
}

// @ts-expect-error Access hidden properties
const {_propTypes: propTypes, _mergedDefaultProps: defaultProps} = Layer;

// Test alternative data formats
testCases.push(...makeAltDataTestCases(sampleProps, propTypes));
testCases.push(...makeAltDataTestCases<LayerT>(sampleProps, propTypes));

for (const propName in Layer.defaultProps) {
if (!(propName in sampleProps)) {
Expand Down Expand Up @@ -113,7 +135,19 @@ export function generateLayerTests({
return testCases;
}

function makeAltPropTestCase({propName, propTypes, defaultProps, sampleProps, assert}) {
function makeAltPropTestCase<LayerT extends Layer>({
propName,
propTypes,
defaultProps,
sampleProps,
assert
}: {
propName: string;
propTypes: DefaultProps<LayerT['props']>;
defaultProps: LayerT['props'];
sampleProps: Partial<LayerT['props']>;
assert: (condition: any, comment: string) => void;
}): LayerTestCase<LayerT>[] | null {
const newProps = {...sampleProps};
const propDef = propTypes[propName];

Expand Down Expand Up @@ -174,7 +208,7 @@ function makeAltPropTestCase({propName, propTypes, defaultProps, sampleProps, as
updateTriggers: {
[propName]: 'function+trigger'
}
},
} as Partial<Layer['props']>,
onBeforeUpdate,
onAfterUpdate
}
Expand All @@ -186,29 +220,34 @@ function makeAltPropTestCase({propName, propTypes, defaultProps, sampleProps, as
}
}

function makeAltDataTestCases(props, propTypes) {
function makeAltDataTestCases<LayerT extends Layer>(
props: Partial<LayerT['props']>,
propTypes: DefaultProps<LayerT['props']>
): LayerTestCase<LayerT>[] {
const originalData = props.data;
if (!Array.isArray(originalData)) {
return [];
}
// partial update
const partialUpdateProps = {
const partialUpdateProps: Partial<Layer['props']> = {
data: originalData.slice(),
_dataDiff: () => [{startRow: 0, endRow: 2}]
};
// data should support any iterable
const genIterableProps = {
const genIterableProps: Partial<Layer['props']> = {
data: new Set(originalData),
_dataDiff: null
};
// data in non-iterable form
const nonIterableProps = {
const nonIterableProps: Partial<Layer['props']> = {
data: {
length: originalData.length
}
};
for (const propName in props) {
// @ts-ignore propName cannot be used as index
if (propTypes[propName].type === 'accessor') {
// @ts-ignore propName cannot be used as index
nonIterableProps[propName] = (_, info) => props[propName](originalData[info.index], info);
}
}
Expand Down
9 changes: 6 additions & 3 deletions modules/test-utils/src/index.ts
Expand Up @@ -11,9 +11,12 @@ export {
export {generateLayerTests} from './generate-layer-tests';

// Basic utility for rendering multiple scenes (could go into "deck.gl/core")
export {default as TestRunner} from './test-runner';
export {TestRunner} from './test-runner';

// A utility that renders a list of scenes and compares against golden images
export {default as SnapshotTestRunner} from './snapshot-test-runner';
export {SnapshotTestRunner} from './snapshot-test-runner';
// A utility that emulates input events
export {default as InteractionTestRunner} from './interaction-test-runner';
export {InteractionTestRunner} from './interaction-test-runner';

export type {SnapshotTestCase} from './snapshot-test-runner';
export type {InteractionTestCase} from './interaction-test-runner';
42 changes: 29 additions & 13 deletions modules/test-utils/src/interaction-test-runner.ts
Expand Up @@ -19,48 +19,64 @@
// THE SOFTWARE.

/* global window */
import TestRunner from './test-runner';
import {TestRunner} from './test-runner';
import type {Deck, Layer} from '@deck.gl/core';

const DEFAULT_TEST_CASE = {
type InteractionEvent =
| {
type: string;
[key: string]: any;
}
| {
wait: number;
};

export type InteractionTestCase = {
name: string;
events: InteractionEvent[];
timeout?: number;
context?: any;
onBeforeEvents: (params: {deck: Deck}) => any;
onAfterEvents: (params: {deck: Deck; layers: Layer[]; context: any}) => void;
};

const DEFAULT_TEST_CASE: InteractionTestCase = {
name: 'Unnamed interaction test',
events: [],
onBeforeEvents: ({deck}) => {},
onAfterEvents: ({deck, layers, context}) => {}
};

function sleep(timeout) {
function sleep(timeout: number): Promise<void> {
return new Promise(resolve => window.setTimeout(resolve, timeout));
}

export default class InteractionTestRunner extends TestRunner {
export class InteractionTestRunner extends TestRunner<InteractionTestCase, {}> {
get defaultTestCase() {
return DEFAULT_TEST_CASE;
}

// chain events
// TODO - switch to async/await
runTestCase(testCase, onDone) {
async runTestCase(testCase: InteractionTestCase) {
testCase.context = testCase.onBeforeEvents({
deck: this.deck
deck: this.deck!
});

let promise = Promise.resolve();
for (const event of testCase.events) {
if (event.wait) {
promise = promise.then(() => sleep(event.wait));
await sleep(event.wait);
} else {
promise = promise.then(() => window.browserTestDriver_emulateInput(event));
await window.browserTestDriver_emulateInput(event);
}
}
return promise.then(onDone);
}

assert(testCase) {
async assert(testCase) {
testCase.onAfterEvents({
deck: this.deck,
// @ts-expect-error Accessing protected layerManager
layers: this.deck.layerManager.getLayers(),
context: testCase.context
});
this._next();
}
}

0 comments on commit 2737b0a

Please sign in to comment.