Skip to content

Commit

Permalink
fix(cypress-harness): fix cypress harness chaining issue with Cypress 12
Browse files Browse the repository at this point in the history
Since Cypress 12, `invoke()` doesn't wait for promises to resolve and just returns the promise as is.
This was fixed by using `then()` instead.

Harness usage without chaining was not affected. i.e. `getHarness(...).then(harness => harness.selectCell(42))`.

Cf. https://docs.cypress.io/guides/references/changelog#12-0-0
Cf. cypress-io/cypress#24417

Closes #232
  • Loading branch information
yjaaidi committed May 22, 2023
1 parent b872c4b commit 06e6c31
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ export class CypressHarnessEnvironment extends HarnessEnvironment<Element> {
this._documentRoot = documentRoot;
}

forceStabilize(): Promise<void> {
throw new Error('Method not implemented.');
async forceStabilize(): Promise<void> {
console.warn(
'`HarnessEnvironment#forceStabilize()` was called but it is a noop in Cypress environment.'
);
}

waitForTasksOutsideAngular(): Promise<void> {
Expand Down
47 changes: 25 additions & 22 deletions packages/cypress-harness/src/lib/internals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,21 @@
import { ComponentHarness } from '@angular/cdk/testing';
import { CypressHarnessEnvironment } from './cypress-harness-environment';

export type ChainableHarness<HARNESS> = Cypress.Chainable<HARNESS> &
{
/* For each field or method... is this a method? */
[K in keyof HARNESS]: HARNESS[K] extends (...args: any) => any
? /* It's a method so let's change the return type. */
(
...args: Parameters<HARNESS[K]>
) => /* Convert Promise<T> to Chainable<T> and anything else U to Chainable<U>. */
ChainableHarness<
ReturnType<HARNESS[K]> extends Promise<infer RESULT>
? RESULT
: HARNESS[K]
>
: /* It's something else. */
export type ChainableHarness<HARNESS> = Cypress.Chainable<HARNESS> & {
/* For each field or method... is this a method? */
[K in keyof HARNESS]: HARNESS[K] extends (...args: any) => any
? /* It's a method so let's change the return type. */
(
...args: Parameters<HARNESS[K]>
) => /* Convert Promise<T> to Chainable<T> and anything else U to Chainable<U>. */
ChainableHarness<
ReturnType<HARNESS[K]> extends Promise<infer RESULT>
? RESULT
: HARNESS[K]
>
: /* It's something else. */
HARNESS[K];
};
};

/**
* Adds harness methods to chainer.
Expand All @@ -30,14 +29,18 @@ export function addHarnessMethodsToChainer<HARNESS extends ComponentHarness>(
chainer: Cypress.Chainable<HARNESS>
): ChainableHarness<HARNESS> {
const handler = {
get: (target: any, prop: string) => (...args: any[]) => {
/* Don't wrap cypress methods like `invoke`, `should` etc.... */
if (prop in target) {
return target[prop](...args);
}
get:
(chainableTarget: any, prop: string) =>
(...args: any[]) => {
/* Don't wrap cypress methods like `invoke`, `should` etc.... */
if (prop in chainableTarget) {
return chainableTarget[prop](...args);
}

return addHarnessMethodsToChainer(target.invoke(prop, ...args));
},
return addHarnessMethodsToChainer(
chainableTarget.then((target: any) => target[prop](...args))
);
},
};

return new Proxy(chainer, handler);
Expand Down
25 changes: 9 additions & 16 deletions tests/cypress-harness-wide/src/date-picker-harness.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import { Component } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { MatNativeDateModule } from '@angular/material/core';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatCalendarHarness, MatDatepickerInputHarness } from '@angular/material/datepicker/testing';
import { MatDatepickerInputHarness } from '@angular/material/datepicker/testing';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { getAllHarnesses, getHarness } from '@jscutlery/cypress-harness';
import { getHarness } from '@jscutlery/cypress-harness';

@Component({
standalone: true,
Expand All @@ -15,14 +15,13 @@ import { getAllHarnesses, getHarness } from '@jscutlery/cypress-harness';
MatFormFieldModule,
MatInputModule,
MatNativeDateModule,
ReactiveFormsModule
ReactiveFormsModule,
],
template: `
<mat-form-field>
<input matInput [matDatepicker]='picker' [formControl]='control' />
<mat-datepicker-toggle matSuffix [for]='picker'></mat-datepicker-toggle>
<mat-datepicker #picker></mat-datepicker>
</mat-form-field>`
template: ` <mat-form-field>
<input matInput [matDatepicker]="picker" [formControl]="control" />
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker #picker></mat-datepicker>
</mat-form-field>`,
})
export class TestedComponent {
control = new FormControl();
Expand All @@ -32,9 +31,6 @@ describe(getHarness.name, () => {
/* getHarness is lazy, so we can share the same reference. */
const datepicker = getHarness(MatDatepickerInputHarness);

/* getAllHarnesses is lazy, so we can instantiate it here and use it later. */
const calendars = getAllHarnesses(MatCalendarHarness);

it('should set date using material datepicker harness', () => {
mountComponent();
datepicker.setValue('1/1/2010');
Expand All @@ -43,9 +39,6 @@ describe(getHarness.name, () => {
datepicker.getCalendar().invoke('next');
datepicker.getCalendar().selectCell({ text: '10' });
datepicker.getValue().should('equal', '2/10/2010');
datepicker.closeCalendar();

calendars.should('be.empty');
});

it('should set date using imperative approach', () => {
Expand All @@ -63,7 +56,7 @@ describe(getHarness.name, () => {

function mountComponent() {
return cy.mount(TestedComponent, {
imports: [BrowserAnimationsModule]
imports: [BrowserAnimationsModule],
});
}
});
9 changes: 4 additions & 5 deletions tests/cypress-harness-wide/src/get-root-harness.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@ import { getRootHarness } from '@jscutlery/cypress-harness';
@Component({
standalone: true,
selector: 'jc-tested',
template: '<input>'
template: '<input>',
})
export class TestedComponent {
}
export class TestedComponent {}

export class TestedHarness extends ComponentHarness {
static hostSelector = 'jc-tested';
Expand All @@ -27,7 +26,7 @@ export class TestedHarness extends ComponentHarness {
describe(getRootHarness.name, () => {
it('should setInputValue', () => {
cy.mount(TestedComponent);
getRootHarness(TestedHarness).invoke('setValue', 'test');
getRootHarness(TestedHarness).invoke('getValue').should('equal', 'test');
getRootHarness(TestedHarness).setValue('test');
getRootHarness(TestedHarness).getValue().should('equal', 'test');
});
});

0 comments on commit 06e6c31

Please sign in to comment.