Skip to content
Open
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
6 changes: 6 additions & 0 deletions .changeset/thin-experts-ring.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@interactors/material-ui": patch
"@interactors/with-storybook": patch
---

Add Storybook addon `with-storybook` package
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"packages/keyboard",
"packages/html",
"packages/material-ui",
"packages/with-cypress"
"packages/with-cypress",
"packages/with-storybook"
]
},
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"prepack:commonjs": "tsc --project ./tsconfig.build.json --outdir dist/cjs --module commonjs"
},
"dependencies": {
"@effection/core": "2.2.0",
"@effection/core": "^2.2.0",
"@interactors/globals": "1.0.0-rc1.1",
"@testing-library/dom": "^8.5.0",
"@testing-library/user-event": "^13.2.1",
Expand Down
27 changes: 5 additions & 22 deletions packages/core/src/interaction.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Operation, Task, run, Symbol } from '@effection/core';
import { InteractionOptions as SerializedInteractionOptions, globals, InteractionType } from '@interactors/globals';
import { globals, CommonInteraction, InteractionType } from '@interactors/globals';
import type { Interactor, FilterObject, FilterFn, FilterParams } from './specification';
import { serializeInteractionOptions } from './serialize';

Expand All @@ -21,36 +21,19 @@ export function isInteraction(x: unknown): x is Interaction<Element, unknown> {
*
* @typeParam T the return value of the promise that this interaction evaluates to.
*/
export interface Interaction<E extends Element, T = void> extends Promise<T> {
type: InteractionType;

export type Interaction<E extends Element, T = void> = CommonInteraction<T> & {
interactor: Interactor<E, any>;

run: (interactor: Interactor<E, any>) => Operation<T>;
/**
* Return a description of the interaction
*/
description: string;
/**
* Return a code representation of the interaction
*/
code: () => string;
/**
* Return a serialized options of the interaction
*/
options: SerializedInteractionOptions;
/**
* Perform the interaction
*/
action: () => Task<T>;

check?: () => Task<T>;

halt: () => Promise<void>;

[interactionSymbol]: true;
}

export interface ActionInteraction<E extends Element, T = void> extends Interaction<E, T> {
export type ActionInteraction<E extends Element, T = void> = Interaction<E, T> & {
type: "action";
check: undefined;
}
Expand All @@ -60,7 +43,7 @@ export interface ActionInteraction<E extends Element, T = void> extends Interact
*
* @typeParam T the return value of the promise that this interaction evaluates to.
*/
export interface AssertionInteraction<E extends Element, T = void> extends Interaction<E, T> {
export type AssertionInteraction<E extends Element, T = void> = Interaction<E, T> & {
type: "assertion";
/**
* Perform the check
Expand Down
2 changes: 1 addition & 1 deletion packages/globals/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,6 @@
"yarn": "1.22.11"
},
"dependencies": {
"@effection/core": "2.2.0"
"@effection/core": "^2.2.0"
}
}
25 changes: 18 additions & 7 deletions packages/globals/src/globals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,25 @@ import { KeyboardLayout } from "./keyboard-layout";

export type InteractionType = "action" | "assertion";

type Interaction<T = any> = Operation<T> & {
export type CommonInteraction<T = any> = Promise<T> & Operation<T> & {
type: InteractionType;
/**
* Return a description of the interaction
*/
description: string;
options: InteractionOptions;
interactor: unknown; // we can't type this any better here
/**
* Return a code representation of the interaction
*/
code: () => string;
/**
* Return a serialized options of the interaction
*/
options: InteractionOptions;
/**
* Cancel interaction execution
*/
halt: () => Promise<void>;
};
}

interface Globals {
readonly document: Document;
Expand Down Expand Up @@ -41,7 +52,7 @@ export type InteractionOptions = InteractorOptions & {
ancestors?: InteractorOptions[];
};

export type InteractionWrapper<T = any> = (perform: () => Promise<T>, interaction: Interaction<T>) => Operation<T>;
export type InteractionWrapper<T = any> = (perform: () => Promise<T>, interaction: CommonInteraction<T>) => Operation<T>;

declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace, @typescript-eslint/prefer-namespace-keyword
Expand All @@ -52,7 +63,7 @@ declare global {
}

if (!globalThis.__interactors) {
let wrapInteraction = <T>(perform: () => Promise<T>, interaction: Interaction<T>): Operation<T> => {
let wrapInteraction = <T>(perform: () => Promise<T>, interaction: CommonInteraction<T>): Operation<T> => {
return (scope) => {
let current = perform;
for (let wrapper of getGlobals().interactionWrappers) {
Expand Down Expand Up @@ -131,7 +142,7 @@ export function addActionWrapper<T>(
wrapper: (description: string, perform: () => Promise<T>, type: InteractionType) => Operation<T>
): () => boolean {
return addInteractionWrapper(
(perform: () => Promise<T>, interaction: Interaction<T>): Operation<T> =>
(perform: () => Promise<T>, interaction: CommonInteraction<T>): Operation<T> =>
wrapper(interaction.description, perform, interaction.type)
);
}
Expand Down
11 changes: 3 additions & 8 deletions packages/globals/test/globals.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { describe, it } from "mocha";
import expect from "expect";
import { JSDOM } from "jsdom";
import { Symbol } from "@effection/core";

import { globals, setDocumentResolver, addInteractionWrapper, setInteractorTimeout } from "../src";
import { globals, setDocumentResolver, addInteractionWrapper, setInteractorTimeout, CommonInteraction } from "../src";

function makeDocument(body = ""): Document {
return new JSDOM(`<!doctype html><html><body>${body}</body></html>`).window.document;
Expand Down Expand Up @@ -46,7 +45,6 @@ describe("@interactors/globals", () => {
type: "action",
interactor: "Interactor",
description: "plain action",
action,
code: () => "",
halt: () => Promise.resolve(),
options: {
Expand All @@ -55,8 +53,7 @@ describe("@interactors/globals", () => {
type: "action",
code: () => "",
},
[Symbol.operation]: Promise.resolve(),
}
} as unknown as CommonInteraction
) as () => unknown)()
).toBe(action);
});
Expand All @@ -70,7 +67,6 @@ describe("@interactors/globals", () => {
type: "action",
interactor: "Interactor",
description: "foo action",
action,
code: () => "",
halt: () => Promise.resolve(),
options: {
Expand All @@ -79,8 +75,7 @@ describe("@interactors/globals", () => {
type: "action",
code: () => "",
},
[Symbol.operation]: Promise.resolve(),
},
} as unknown as CommonInteraction,
);

removeWrapper();
Expand Down
10 changes: 8 additions & 2 deletions packages/material-ui/.storybook/main.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
module.exports = {
stories: ["../stories/**/*.stories.@(md|ts)x"],
addons: ["@storybook/addon-postcss", "@storybook/addon-essentials"],
features: { previewCsfV3: true },
addons: [
"@storybook/addon-postcss",
"@storybook/addon-essentials",
"@storybook/addon-interactions",
"@interactors/with-storybook",
],
features: { previewCsfV3: true, interactionsDebugger: true },
core: { builder: "webpack5" },
typescript: { reactDocgen: false },
};
15 changes: 9 additions & 6 deletions packages/material-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,13 @@
"@material-ui/icons": "^4.11.2",
"@material-ui/pickers": "^3.3.10",
"@material-ui/styles": "^4.11.4",
"@storybook/addon-docs": "6.4.0-alpha.30",
"@storybook/addon-essentials": "6.4.0-alpha.30",
"@storybook/addon-docs": "6.4.19",
"@storybook/addon-essentials": "6.4.19",
"@storybook/addon-interactions": "^6.4.19",
"@storybook/addon-postcss": "^2.0.0",
"@storybook/builder-webpack5": "6.4.0-alpha.30",
"@storybook/manager-webpack5": "6.4.0-alpha.30",
"@storybook/react": "6.4.0-alpha.30",
"@storybook/builder-webpack5": "6.4.19",
"@storybook/manager-webpack5": "6.4.19",
"@storybook/react": "6.4.19",
"@testing-library/react": "^12.0.0",
"@types/react": "^17.0.19",
"bigtest": "^0.16.0",
Expand All @@ -64,6 +65,8 @@
"webpack": "^5.53.0"
},
"dependencies": {
"@interactors/html": "1.0.0-rc1.2"
"effection": "^2.0.4",
"@interactors/html": "1.0.0-rc1.2",
"@interactors/with-storybook": "1.0.0-rc1.2"
}
}
2 changes: 1 addition & 1 deletion packages/material-ui/src/body.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { HTML } from "@interactors/html";
export const Body = HTML.extend<HTMLBodyElement>("Body")
.selector("body")
.actions({
click: ({ perform }) =>
click: async ({ perform }) =>
perform((element) => {
if (document.activeElement && "blur" in document.activeElement) {
(document.activeElement as HTMLElement).blur();
Expand Down
4 changes: 2 additions & 2 deletions packages/material-ui/src/bottom-navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const BottomNavigationAction = createInteractor<HTMLButtonElement>("MUIBottomNav
let label = element.querySelector('[class*="MuiBottomNavigationAction-label"]');
return isHTMLElement(label) ? innerText(label) : "";
})
.actions({ click: ({ perform }) => perform((element) => click(element)) });
.actions({ click: async ({ perform }) => perform((element) => click(element)) });

const BottomNavigationInteractor = createInteractor<HTMLElement>("MUIBottomNavigation")
.selector('[class*="MuiBottomNavigation-root"]')
Expand All @@ -18,7 +18,7 @@ const BottomNavigationInteractor = createInteractor<HTMLElement>("MUIBottomNavig
},
})
.actions({
navigate: (interactor, value: string) => interactor.find(BottomNavigationAction(value)).click(),
navigate: async (interactor, value: string) => interactor.find(BottomNavigationAction(value)).click(),
});

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/material-ui/src/checkbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const CheckboxInteractor = BaseCheckbox.extend("MUICheckbox")
},
})
.actions({
click: ({ perform }) =>
click: async ({ perform }) =>
perform((element) => {
element.focus();
click(element);
Expand Down
2 changes: 1 addition & 1 deletion packages/material-ui/src/date-field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const DateFieldInteractor = TextField.extend<HTMLInputElement>("DateField")
timestamp: (element) => element.valueAsNumber,
})
.actions({
fillIn: ({ perform }, value: string | Date) =>
fillIn: async ({ perform }, value: string | Date) =>
perform((element) => {
setValue(element, typeof value == "string" ? value : value.toISOString().replace(/T.*$/, ""));
dispatchChange(element);
Expand Down
2 changes: 1 addition & 1 deletion packages/material-ui/src/datetime-field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const DateTimeFieldInteractor = TextField.extend<HTMLInputElement>("DateTimeFiel
.selector('input[type="datetime-local"]')
.filters({ timestamp: (element) => element.valueAsNumber })
.actions({
fillIn: ({ perform }, value: string | Date) =>
fillIn: async ({ perform }, value: string | Date) =>
perform((element) => {
setValue(element, typeof value == "string" ? value : value.toISOString().replace(/Z$/, ""));
dispatchChange(element);
Expand Down
2 changes: 1 addition & 1 deletion packages/material-ui/src/dialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const DialogInteractor = HTML.extend("MUIDialog")
return isHTMLElement(titleElement) ? innerText(titleElement) : "";
})
.actions({
close: ({ perform }) =>
close: async ({ perform }) =>
perform((element) => {
let backdrop = element.querySelector('[class*="MuiBackdrop-root"]');
if (isHTMLElement(backdrop)) click(backdrop);
Expand Down
71 changes: 71 additions & 0 deletions packages/material-ui/stories/accordion.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { ComponentMeta, ComponentStoryObj } from '@storybook/react'
import { Accordion, matching, some } from "../src";
import {
Accordion as Component,
AccordionSummary,
AccordionDetails,
AccordionActions,
Button
} from "@material-ui/core";
import { renderComponent } from "./helpers";
import { cloneElement } from "react";

export default {
title: 'Accordion',
component: renderComponent(Component, {}, ({ props, children }) => (
cloneElement(
children(props),
{},
<AccordionSummary aria-label="accordion">Expand</AccordionSummary>,
<AccordionDetails>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Suspendisse malesuada lacus ex, sit amet blandit leo lobortis eget.
</AccordionDetails>,
<AccordionActions>
<Button>Ok</Button>
</AccordionActions>
)
))
} as ComponentMeta<typeof Component>

const accordion = Accordion("accordion");

export const Default: ComponentStoryObj<typeof Component> = {
async play() {
await accordion.exists();
await accordion.is({ expanded: false })
await accordion.has({ classList: some(matching(/MuiAccordion-root(-\d+)?/)) })
await Accordion({ disabled: false }).exists()
}
}

export const TestExpandAction: ComponentStoryObj<typeof Component> = {
async play() {
await accordion.expand()
await accordion.is({ expanded: true })
}
}

export const TestCollapseAction: ComponentStoryObj<typeof Component> = {
async play() {
await accordion.expand()
await accordion.collapse()
await accordion.is({ expanded: false })
}
}

export const TestToggleAction: ComponentStoryObj<typeof Component> = {
async play() {
await accordion.toggle()
await accordion.is({ expanded: true })
await accordion.toggle()
await accordion.is({ expanded: false })
}
}

export const Disabled: ComponentStoryObj<typeof Component> = {
args: { disabled: true },
async play() {
await Accordion({ disabled: true }).exists()
}
}
Loading