Skip to content

Commit

Permalink
feat(nova-react-test-utils): add storybook decorator for nova (#72)
Browse files Browse the repository at this point in the history
* install storybook and bump graphitation

move operation utils

make mock environment a bit more generic

add first version of the decorator

add peer dependency

try bumping graphitation relay complier as well

Change files

initial example

use older version of complier

generate types

add storybook

[DO NOT MERGE] add autogenerated stories

fix lage config

remove stories

align version of test types

use makeDecorator api

add codegen

add initial story

add more loaders

use proper loader

* fix schema loading and decorator

* [almost] working

* add sample tests

* add test using nova test utils directly

* fix mutation

* one step closer

* fix issues with typename

* add error message handling partial

* use latest

* Change files

* fix react version mismatch

* update dependencies

* add working reject story

* add loading state test

* add explanatory comment

* fix storybook warning

* add another explanatory comment

* use satisfies and add comment about storybook change

* update docs

* adjust readme

* update versions

* bump storybook and use id

* remove redundant optional chaining

* Update packages/nova-react-test-utils/README.md

Co-authored-by: Eloy Durán <[email protected]>

* simplify preview

* adjust readme

* fix warning inside test

* update to set environment on parameters

* fix typo

---------

Co-authored-by: Stanislaw Wilczynski <[email protected]>
Co-authored-by: Eloy Durán <[email protected]>
  • Loading branch information
3 people authored May 17, 2023
1 parent dc35988 commit 3d65a5b
Show file tree
Hide file tree
Showing 37 changed files with 7,776 additions and 782 deletions.
7 changes: 2 additions & 5 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"es6": true,
"node": true
},
"ignorePatterns": ["**/coverage", "**/lib", "**/temp"],
"ignorePatterns": ["**/coverage", "**/lib", "**/temp", "**/__generated__"],
"plugins": ["@typescript-eslint"],
"rules": {
"@typescript-eslint/consistent-type-imports": [
Expand All @@ -20,8 +20,5 @@
}
]
},
"extends": [
"plugin:@typescript-eslint/recommended",
"prettier"
]
"extends": ["plugin:@typescript-eslint/recommended", "prettier"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "major",
"comment": "add examples package",
"packageName": "@nova/examples",
"email": "[email protected]",
"dependentChangeType": "patch"
}
7 changes: 7 additions & 0 deletions change/@nova-react-38a57d03-7f69-4197-b2f2-4ba427896bb8.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "bump version of relay-compiler-language-graphitation",
"packageName": "@nova/react",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "major",
"comment": "add storybook decorator for nova components",
"packageName": "@nova/react-test-utils",
"email": "[email protected]",
"dependentChangeType": "patch"
}
7 changes: 7 additions & 0 deletions change/@nova-types-3378b7b7-3f6f-45d5-82ce-687385f75b70.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "bump deps",
"packageName": "@nova/types",
"email": "[email protected]",
"dependentChangeType": "patch"
}
1 change: 1 addition & 0 deletions lage.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module.exports = {
pipeline: {
prepare: [],
types: ["^types"],
build: [],
test: ["build"],
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"test": "lage test",
"lint": "lage lint",
"lage": "lage",
"prepare": "lage prepare",
"ci": "yarn lage build types test lint && yarn checkchange",
"beachball": "beachball -b origin/main",
"change": "yarn beachball change",
Expand Down
4 changes: 4 additions & 0 deletions packages/examples/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": ["../../.eslintrc.json"],
"root": true
}
42 changes: 42 additions & 0 deletions packages/examples/.storybook/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type { StorybookConfig } from "@storybook/react-webpack5";
const config: StorybookConfig = {
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(ts|tsx)"],
addons: [
"@storybook/addon-links",
"@storybook/addon-essentials",
"@storybook/addon-interactions",
],
framework: {
name: "@storybook/react-webpack5",
options: {},
},
webpackFinal: (config) => {
return {
...config,
module: {
...config.module,
rules: [
...(config?.module?.rules ?? []),
{
test: /\.(tsx|ts)$/,
use: [
{
loader: require.resolve("esbuild-loader"),
options: {
loader: "tsx",
target: "es2020",
},
},
],
exclude: /node_modules/,
},
{
test: /\.(graphql)$/,
loader: "@graphql-tools/webpack-loader",
},
],
},
};
},
};
export default config;
5 changes: 5 additions & 0 deletions packages/examples/.storybook/preview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { Preview } from "@storybook/react";

const preview: Preview = {};

export default preview;
3 changes: 3 additions & 0 deletions packages/examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Nova examples

This package contains examples of nova components as well as related good testing approaches for them.
23 changes: 23 additions & 0 deletions packages/examples/codegen.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
overwrite: true
schema:
- "./src/schema.graphql"
generates:
src/__generated__/schema.all.interface.ts:
plugins:
- add:
content:
- // THIS FILE WAS AUTOMATICALLY GENERATED AND SHOULD NOT BE EDITED
-
- "typescript"
- "typescript-resolvers"
- "@graphitation/graphql-codegen-typescript-typemap-plugin"

config:
avoidOptionals: true
preResolveTypes: true
skipTypeNameForRoot: true
exportFragmentSpreadSubTypes: true
# skipTypename: true
useTypeImports: true
allowParentTypeOverride: true
enumsAsTypes: true
56 changes: 56 additions & 0 deletions packages/examples/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{
"name": "@nova/examples",
"license": "MIT",
"version": "0.0.1",
"main": "./src/index.ts",
"scripts": {
"build": "monorepo-scripts build",
"generate": "graphql-codegen --config codegen.yml && nova-graphql-compiler --schema ./src/schema.graphql --src ./src --watchman false",
"lint": "monorepo-scripts lint",
"prepare": "yarn generate",
"test": "monorepo-scripts test",
"types": "monorepo-scripts types",
"just": "monorepo-scripts",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
},
"dependencies": {
"@nova/react": "1.2.1",
"@nova/react-test-utils": "3.1.3",
"@nova/types": "1.3.0",
"graphql": "^15.5.0",
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"devDependencies": {
"@babel/core": "^7.20.2",
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.21.0",
"@graphitation/graphql-codegen-typescript-typemap-plugin": "^0.1.5",
"@graphitation/graphql-js-tag": "^0.9.0",
"@graphql-codegen/add": "^4.0.1",
"@graphql-codegen/cli": "^3.2.2",
"@graphql-codegen/typescript": "^3.0.2",
"@graphql-codegen/typescript-resolvers": "^3.1.1",
"@graphql-tools/webpack-loader": "^6.7.1",
"@storybook/addon-essentials": "^7.0.11",
"@storybook/addon-interactions": "^7.0.11",
"@storybook/addon-links": "^7.0.11",
"@storybook/blocks": "^7.0.11",
"@storybook/react": "^7.0.11",
"@storybook/react-webpack5": "^7.0.11",
"@storybook/testing-library": "^0.1.0",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^12.0.0",
"@types/jest": "^29.2.0",
"@types/react": "^17.0.2",
"@types/react-dom": "^17.0.11",
"esbuild-loader": "^3.0.1",
"monorepo-scripts": "*",
"prop-types": "15.8.1",
"storybook": "^7.0.11",
"typescript": ">=4.9.5"
},
"sideEffects": false
}
69 changes: 69 additions & 0 deletions packages/examples/src/Feedback/Feedback.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { graphql, useFragment, useMutation } from "@nova/react";
import * as React from "react";
import type { FeedbackComponent_LikeMutation } from "./__generated__/FeedbackComponent_LikeMutation.graphql";
import type { Feedback_feedbackFragment$key } from "./__generated__/Feedback_feedbackFragment.graphql";

type Props = {
feedback: Feedback_feedbackFragment$key;
};

export const Feedback_feedbackFragment = graphql`
fragment Feedback_feedbackFragment on Feedback {
id
message {
text
}
doesViewerLike
}
`;

export const FeedbackComponent = (props: Props) => {
const [errorMessage, setErrorMessage] = React.useState<string | null>(null);
const feedback = useFragment(Feedback_feedbackFragment, props.feedback);
const [like, isPending] = useMutation<FeedbackComponent_LikeMutation>(
graphql`
mutation FeedbackComponent_LikeMutation($input: FeedbackLikeInput!) {
feedbackLike(input: $input) {
feedback {
id
doesViewerLike
}
}
}
`,
);
return (
<div>
{errorMessage != null && (
<div style={{ color: "red" }}>{errorMessage}</div>
)}
Feedback: {feedback.message.text}
<button
id="likeButton"
disabled={isPending}
onClick={() => {
like({
variables: {
input: {
feedbackId: feedback.id,
doesViewerLike: !feedback.doesViewerLike,
},
},
optimisticResponse: {
feedbackLike: {
feedback: {
id: feedback.id,
doesViewerLike: !feedback.doesViewerLike,
},
},
},
}).catch(() => {
setErrorMessage("Something went wrong");
});
}}
>
{feedback.doesViewerLike ? "Unlike" : "Like"}
</button>
</div>
);
};
104 changes: 104 additions & 0 deletions packages/examples/src/Feedback/FeedbackContainer.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import type { Meta, StoryObj } from "@storybook/react";
import { userEvent, within } from "@storybook/testing-library";
import { FeedbackContainer } from "./FeedbackContainer";
import type { NovaEnvironmentDecoratorParameters } from "@nova/react-test-utils";
import {
getNovaEnvironmentForStory,
getNovaEnvironmentDecorator,
MockPayloadGenerator,
} from "@nova/react-test-utils";
import { getSchema } from "../testing-utils/getSchema";
import type { TypeMap } from "../__generated__/schema.all.interface";

const schema = getSchema();

const meta: Meta<typeof FeedbackContainer> = {
component: FeedbackContainer,
decorators: [getNovaEnvironmentDecorator(schema)],
};

export default meta;
type Story = StoryObj<typeof meta>;

export const Primary: Story = {
parameters: {
novaEnvironment: {
resolvers: {
Feedback: () => sampleFeedback,
},
},
} satisfies NovaEnvironmentDecoratorParameters<TypeMap>,
};

export const Liked: Story = {
parameters: {
novaEnvironment: {
resolvers: {
Feedback: () => ({
...sampleFeedback,
doesViewerLike: true,
}),
},
},
} satisfies NovaEnvironmentDecoratorParameters<TypeMap>,
};

export const Like: Story = {
parameters: {
novaEnvironment: {
resolvers: {
Feedback: () => sampleFeedback,
FeedbackLikeMutationResult: () => ({
feedback: {
...sampleFeedback,
doesViewerLike: true,
},
}),
},
},
} satisfies NovaEnvironmentDecoratorParameters<TypeMap>,
play: async ({ canvasElement }) => {
const container = within(canvasElement);
const likeButton = await container.findByRole("button", { name: "Like" });
userEvent.click(likeButton);
},
};

export const LikeFailure: Story = {
parameters: {
novaEnvironment: {
enableQueuedMockResolvers: false,
},
} satisfies NovaEnvironmentDecoratorParameters<TypeMap>,
play: async (context) => {
const {
graphql: { mock },
} = getNovaEnvironmentForStory(context);

// wait for next tick for apollo client to update state
await new Promise((resolve) => setTimeout(resolve, 0));
await mock.resolveMostRecentOperation((operation) =>
MockPayloadGenerator.generate(operation, {
Feedback: () => sampleFeedback,
}),
);
await Like.play?.(context);
mock.rejectMostRecentOperation(new Error("Like failed"));
},
};

export const Loading: Story = {
parameters: {
novaEnvironment: {
enableQueuedMockResolvers: false,
},
} satisfies NovaEnvironmentDecoratorParameters<TypeMap>,
};

const sampleFeedback = {
id: "42",
message: {
text: "Feedback title",
},
doesViewerLike: false,
};
Loading

0 comments on commit 3d65a5b

Please sign in to comment.