Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Storybook integration #199

Merged
merged 23 commits into from
Sep 29, 2023
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
904f9be
dev(storybook): added default Storybook config
Sep 7, 2023
e143ebe
dev(storybook): stories can live anywhere
Sep 7, 2023
f34b6d7
dev(storybook): created stories for Button
Sep 7, 2023
4fcd069
dev(storybook): add CSS to pages
Sep 7, 2023
16e1b49
dev(storybook): added search stories
Sep 7, 2023
35ad554
dev(storybook): added Header stories
Sep 7, 2023
fdb315a
dev(storybook): added TestHarness
Sep 7, 2023
704f4d0
dev(storybook): added extra Search stories
Sep 12, 2023
c878707
chore: removed outdated references to Sanity's deployment, since it's…
Sep 14, 2023
3b31e05
types: added Storybook tests to CI
Sep 19, 2023
357ba08
dev(storybook): extracted TestHarness
Sep 19, 2023
d344166
types: added Search default test
Sep 19, 2023
a2db711
dev(storybook): added playwright as explicit dependency
Sep 19, 2023
3ff1cdb
dev(storybook): bumped start-server
Sep 19, 2023
6125ef4
dev(storybook): improved assertions
Sep 19, 2023
a04740b
dev(storybook): improved error messages
Sep 19, 2023
f027cf9
dev(storybook): configure CI for Storybook
Sep 19, 2023
994f1b4
dev(storybook): run Storybook in parallel
Sep 19, 2023
cdd1de9
dev(storybook): consistent `Next.js` usage
Sep 20, 2023
6831e49
Storybook: Use plop file generator for creating new Stories (#200)
scottrippey Sep 28, 2023
c14b6e4
Storybook: visual regression testing via Chromatic (#203)
scottrippey Sep 28, 2023
7b760d1
Search: update list item line color (#204)
scottrippey Sep 28, 2023
b9b48ad
dev(storybook): disable mocks in `.env.sample`
Sep 29, 2023
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
75 changes: 66 additions & 9 deletions .github/workflows/code-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ on:
branches:
- main
pull_request:
branches:
- main

env:
NEXT_PUBLIC_SANITY_PROJECT_ID: 5bsv02jj

jobs:
build:
name: Check codebase (lint and typecheck)
checks:
name: "Check codebase (lint and typecheck)"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
Expand All @@ -22,23 +23,79 @@ jobs:
with:
version: 7

- name: Get pnpm store directory
- name: "Get pnpm store directory"
scottrippey marked this conversation as resolved.
Show resolved Hide resolved
id: pnpm-cache
run: echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"

- name: Setup pnpm cache
- name: "Setup pnpm cache"
uses: actions/cache@v3
with:
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('./pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-

- name: Install dependencies
- name: "Install dependencies"
run: pnpm install

- name: Lint
- name: "Lint"
run: pnpm run lint

- name: TypeCheck
- name: "TypeCheck"
run: pnpm run typecheck

tests:
name: "Unit Tests"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: actions/setup-node@v2
with:
node-version: 18.x

- uses: pnpm/[email protected]
with:
version: 7

- name: "Get pnpm store directory"
id: pnpm-cache
run: echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"

- name: "Setup pnpm cache"
uses: actions/cache@v3
with:
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('./pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-

- name: "Install dependencies"
run: pnpm install

- name: "Configure .env"
working-directory: packages/nextjs
run: cp .env.sample .env

- name: "Storybook: Install Extra Dependencies"
working-directory: packages/nextjs
run: pnpm exec playwright install

- name: "Storybook: Build"
working-directory: packages/nextjs
run: pnpm run storybook:build

- name: "Storybook: Chromatic"
uses: chromaui/action@v1
# Chromatic GitHub Action options
with:
workingDir: packages/nextjs
storybookBuildDir: storybook-static
exitOnceUploaded: true
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}

- name: "Storybook: Run Tests"
working-directory: packages/nextjs
run: pnpm run test:storybook:start

40 changes: 0 additions & 40 deletions .github/workflows/deploySanity.yml

This file was deleted.

2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,5 @@ yarn-error.log*
dist
.idea
.vscode

storybook-static/
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"name": "next-sanity-fastly-ecomm-demo",
"name": "nextjs-sanity-fe",
"version": "0.0.1",
"description": "A demo ecommerce site using Next.js, Sanity CMS, and Fastly.",
"keywords": [],
"author": "Formidable",
"main": "index.js",
"license": "MIT",
"scripts": {
"local": "pnpm run --filter ./packages/nextjs --filter ./packages/sanity --parallel dev",
"plop": "plop",
"dev:nextjs": "pnpm run --filter ./packages/nextjs dev",
"next:dev": "pnpm run --filter ./packages/nextjs next:dev",
"next:dev:mock": "pnpm run --filter ./packages/nextjs next:dev:mock",
Expand All @@ -31,8 +31,11 @@
"eslint": "^8.31.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-prettier": "^4.2.1",
"glob": "^10.3.4",
"husky": "^8.0.3",
"inquirer-autocomplete-prompt": "^3.0.0",
"lint-staged": "^13.1.0",
"plop": "^4.0.0",
"prettier": "^2.8.2",
"start-server-and-test": "^1.15.2",
"typescript": "4.9.4"
Expand Down
6 changes: 5 additions & 1 deletion packages/nextjs/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
{
"extends": ["../../.eslintrc.json", "next/core-web-vitals"],
"extends": [
"../../.eslintrc.json",
"next/core-web-vitals",
"plugin:storybook/recommended"
],
"rules": {
"import/order": [
"error",
Expand Down
25 changes: 25 additions & 0 deletions packages/nextjs/.storybook/decorators/TestHarness.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { FC, PropsWithChildren } from "react";
import { Decorator } from "@storybook/react";
import { MotionConfig, MotionConfigProps } from "framer-motion";
import { TypedParameters } from "../types";

declare module "../types" {
export interface TypedParameters extends TestHarnessProps {}
}

export type TestHarnessProps = {
motionConfig?: MotionConfigProps;
};

export const TestHarness: FC<PropsWithChildren<TestHarnessProps>> = ({ motionConfig, children }) => {
return <MotionConfig {...motionConfig}>{children}</MotionConfig>;
};

export const TestHarnessDecorator: Decorator = (Story, ctx) => {
scottrippey marked this conversation as resolved.
Show resolved Hide resolved
const { motionConfig } = ctx.parameters as TypedParameters;
return (
<TestHarness motionConfig={motionConfig}>
<Story />
</TestHarness>
);
};
19 changes: 19 additions & 0 deletions packages/nextjs/.storybook/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { StorybookConfig } from "@storybook/nextjs";

const config: StorybookConfig = {
stories: ["../**/*.mdx", "../**/*.stories.@(js|jsx|mjs|ts|tsx)"],
addons: [
"@storybook/addon-links",
"@storybook/addon-essentials",
"@storybook/addon-onboarding",
"@storybook/addon-interactions",
],
framework: {
name: "@storybook/nextjs",
options: {},
},
docs: {
autodocs: "tag",
},
};
export default config;
22 changes: 22 additions & 0 deletions packages/nextjs/.storybook/preview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { Preview } from "@storybook/react";

import "../styles/global.css";
import { TestHarnessDecorator } from "./decorators/TestHarness";

const preview: Preview = {
parameters: {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
},
decorators: [
// List all global decorators:
TestHarnessDecorator,
],
};

export default preview;
29 changes: 29 additions & 0 deletions packages/nextjs/.storybook/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { StoryObj as _StoryObj, Meta as _Meta } from "@storybook/react";

/*
Storybook does not give us strongly-typed parameters,
so we're going to export our own overrides.
*/

export interface TypedParameters {
/*
This interface intentionally left blank,
so that it can be extended by the decorators that use it.
*/
}

type Override<TOriginal, TOverrides> = TOverrides & Omit<TOriginal, keyof TOverrides>;

export type StoryObj<TComponent> = Override<
_StoryObj<TComponent>,
{
parameters?: TypedParameters;
}
>;

export type Meta<TComponent> = Override<
_Meta<TComponent>,
{
parameters?: TypedParameters;
}
>;
46 changes: 46 additions & 0 deletions packages/nextjs/__plop_templates/COMPONENT.stories.tsx.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import type { Meta, StoryObj } from ".storybook/types";

import { screen, userEvent, waitFor, within } from "@storybook/testing-library";
import { expect, jest } from "@storybook/jest";

import { {{COMPONENT.BASENAME}} } from "./{{COMPONENT.BASENAME}}";

const meta: Meta<typeof {{COMPONENT.BASENAME}}> = {
component: {{COMPONENT.BASENAME}},
args: {},
};

export default meta;

type Story = StoryObj<typeof {{COMPONENT.BASENAME}}>;

export const Default: Story = {
async play({ canvasElement, step }) {
const ui = wrap(canvasElement);
await step("TODO: add unit tests", async () => {
expect(ui.TODO).toBeVisible();
});
},
};

export const Variant: Story = {
args: {
// TODO: Customize the variant args here
},
async play({ canvasElement, step }) {
const ui = wrap(canvasElement);
await step("TODO: add unit tests", async () => {
expect(ui.TODO).toBeVisible();
});
},
};

/** Encapsulate all UI elements here for easier testing */
function wrap(canvasElement: HTMLElement) {
const container = within(canvasElement);
return {
get TODO() {
return container.getByRole("TODO");
},
};
}
49 changes: 49 additions & 0 deletions packages/nextjs/components/Button.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import type { Meta, StoryObj } from ".storybook/types";
import { Button } from "./Button";

const meta: Meta<typeof Button> = {
component: Button,
args: {
children: <>Button</>,
},
};
export default meta;

type Story = StoryObj<typeof Button>;

export const Variants: Story = {
render() {
return (
<div className="flex flex-col items-start p-2 gap-2 bg-gray">
<Button variant="primary">Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="tertiary">Tertiary</Button>
<Button variant="text">Text</Button>
</div>
);
},
};

export const Primary: Story = {
args: {
variant: "primary",
},
};

export const Secondary: Story = {
args: {
variant: "secondary",
},
};

export const Tertiary: Story = {
args: {
variant: "tertiary",
},
};

export const Text: Story = {
args: {
variant: "text",
},
};
12 changes: 12 additions & 0 deletions packages/nextjs/components/Header/Header.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { Meta, StoryObj } from ".storybook/types";
import { Header } from "./Header";

const meta: Meta<typeof Header> = {
component: Header,
};

export default meta;

type Story = StoryObj<typeof Header>;

export const Default: Story = {};
Loading
Loading