Skip to content

Commit

Permalink
Merge pull request #781 from chromaui/docs_intro_storybook_angular_up…
Browse files Browse the repository at this point in the history
…dates

Docs: Intro Storybook Angular updates for the latest release (8.3)
  • Loading branch information
jonniebigodes authored Sep 26, 2024
2 parents f8447c8 + 230bce1 commit e0a2d2b
Show file tree
Hide file tree
Showing 10 changed files with 72 additions and 77 deletions.
21 changes: 9 additions & 12 deletions content/intro-to-storybook/angular/en/composite-component.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ Next, create `Tasklist`’s test states in the story file.
```ts:title=src/app/components/task-list.stories.ts
import type { Meta, StoryObj } from '@storybook/angular';

import { argsToTemplate, componentWrapperDecorator, moduleMetadata } from '@storybook/angular';
import { componentWrapperDecorator, moduleMetadata } from '@storybook/angular';

import { CommonModule } from '@angular/common';

Expand All @@ -92,14 +92,9 @@ const meta: Meta<TaskListComponent> = {
(story) => `<div style="margin: 3em">${story}</div>`
),
],
render: (args: TaskListComponent) => ({
props: {
...args,
onPinTask: TaskStories.actionsData.onPinTask,
onArchiveTask: TaskStories.actionsData.onArchiveTask,
},
template: `<app-task-list ${argsToTemplate(args)}></app-task-list>`,
}),
args: {
...TaskStories.ActionsData,
},
};
export default meta;
type Story = StoryObj<TaskListComponent>;
Expand Down Expand Up @@ -146,10 +141,12 @@ export const Empty: Story = {
```

<div class="aside">
💡 <a href="https://storybook.js.org/docs/angular/writing-stories/decorators"><b>Decorators</b></a> are a way to provide arbitrary wrappers to stories. In this case we’re using a decorator key on the default export to add some <code>padding</code> around the rendered component. They can also be used to wrap stories in “providers”–-i.e., library components that set some context.

💡[**Decorators**](https://storybook.js.org/docs/writing-stories/decorators) are a way to provide arbitrary wrappers to stories. In this case we’re using a decorator key on the default export to add some `margin` around the rendered component. hey can also be used to wrap stories in “providers”–-i.e., library components that set some context.

</div>

By importing `TaskStories`, we were able to [compose](https://storybook.js.org/docs/angular/writing-stories/args#args-composition) the arguments (args for short) in our stories with minimal effort. That way, the data and actions (mocked callbacks) expected by both components are preserved.
By importing `TaskStories`, we were able to [compose](https://storybook.js.org/docs/writing-stories/args#args-composition) the arguments (args for short) in our stories with minimal effort. That way, the data and actions (mocked callbacks) expected by both components are preserved.

Now check Storybook for the new `TaskList` stories.

Expand Down Expand Up @@ -236,7 +233,7 @@ The added markup results in the following UI:

<video autoPlay muted playsInline loop>
<source
src="/intro-to-storybook/finished-tasklist-states-6-0.mp4"
src="/intro-to-storybook/finished-tasklist-states-7-0.mp4"
type="video/mp4"
/>
</video>
Expand Down
2 changes: 1 addition & 1 deletion content/intro-to-storybook/angular/en/conclusion.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Storybook is a powerful tool for React, React Native, Vue, Angular, Svelte and m

Want to dive deeper? Here are helpful resources.

- [**Official Storybook documentation**](https://storybook.js.org/docs/angular/get-started/introduction) has API documentation, community links, and the addon gallery.
- [**Official Storybook documentation**](https://storybook.js.org/docs/get-started/install?renderer=angular) has API documentation, community links, and the addon gallery.

- [**UI Testing Playbook**](https://storybook.js.org/blog/ui-testing-playbook/) highlights workflow best practices used by high-velocity teams at Twilio, Adobe, Peloton, and Shopify.

Expand Down
15 changes: 5 additions & 10 deletions content/intro-to-storybook/angular/en/data.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ In `src/app/components/pure-task-list.component.ts`:
```diff:title=src/app/components/pure-task-list.component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { Task } from '../models/task.model';

@Component({
- selector:'app-task-list',
+ selector: 'app-pure-task-list',
Expand Down Expand Up @@ -304,12 +305,11 @@ The reason to keep the presentational version of the `TaskList` separate is that
```ts:title=src/app/components/pure-task-list.stories.ts
import type { Meta, StoryObj } from '@storybook/angular';

import { argsToTemplate, componentWrapperDecorator, moduleMetadata } from '@storybook/angular';
import { componentWrapperDecorator, moduleMetadata } from '@storybook/angular';

import { CommonModule } from '@angular/common';

import PureTaskListComponent from './pure-task-list.component';

import TaskComponent from './task.component';

import * as TaskStories from './task.stories';
Expand All @@ -329,14 +329,9 @@ const meta: Meta<PureTaskListComponent> = {
(story) => `<div style="margin: 3em">${story}</div>`
),
],
render: (args: PureTaskListComponent) => ({
props: {
...args,
onPinTask: TaskStories.actionsData.onPinTask,
onArchiveTask: TaskStories.actionsData.onArchiveTask,
},
template: `<app-pure-task-list ${argsToTemplate(args)}></app-pure-task-list>`,
}),
args: {
...TaskStories.ActionsData,
},
};
export default meta;
type Story = StoryObj<PureTaskListComponent>;
Expand Down
21 changes: 14 additions & 7 deletions content/intro-to-storybook/angular/en/deploy.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Running `npm run build-storybook` will output a static Storybook in the `storybo

## Publish Storybook

This tutorial uses <a href="https://www.chromatic.com/?utm_source=storybook_website&utm_medium=link&utm_campaign=storybook">Chromatic</a>, a free publishing service made by the Storybook maintainers. It allows us to deploy and host our Storybook safely and securely in the cloud.
This tutorial uses [Chromatic](https://www.chromatic.com/?utm_source=storybook_website&utm_medium=link&utm_campaign=storybook), a free publishing service made by the Storybook maintainers. It allows us to deploy and host our Storybook safely and securely in the cloud.

### Set up a repository in GitHub

Expand Down Expand Up @@ -94,20 +94,27 @@ jobs:
runs-on: ubuntu-latest
# Job steps
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0
- run: yarn
#👇 Adds Chromatic as a step in the workflow
- uses: chromaui/action@v1
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies
run: npm ci
- name: "Run Chromatic"
uses: chromaui/action@latest
# Options required for Chromatic's GitHub Action
with:
#👇 Chromatic projectToken, see https://storybook.js.org/tutorials/intro-to-storybook/angular/en/deploy/ to obtain it
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
token: ${{ secrets.GITHUB_TOKEN }}
```

<div class="aside"><p>💡 For brevity purposes <a href=" https://docs.github.com/en/actions/security-guides/encrypted-secrets#creating-encrypted-secrets-for-a-repository ">GitHub secrets</a> weren't mentioned. Secrets are secure environment variables provided by GitHub so that you don't need to hard code the <code>project-token</code>.</p></div>
<div class="aside">

💡 For brevity purposes [GitHub secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets#creating-encrypted-secrets-for-a-repository) weren't mentioned. Secrets are secure environment variables provided by GitHub so that you don't need to hard code the `project-token`.

</div>

### Commit the action

Expand Down
2 changes: 1 addition & 1 deletion content/intro-to-storybook/angular/en/get-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Now we can quickly check that the various environments of our application are wo

```shell:clipboard=false
# Start the component explorer on port 6006:
ng run taskbox:storybook
npm run storybook
# Run the frontend app proper on port 4200:
ng serve
Expand Down
24 changes: 14 additions & 10 deletions content/intro-to-storybook/angular/en/screen.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,9 @@ One way to sidestep this problem is to never render container components anywher
However, developers **will** inevitably need to render containers further down the component hierarchy. If we want to render most or all of the app in Storybook (we do!), we need a solution to this issue.

<div class="aside">
💡 As an aside, passing data down the hierarchy is a legitimate approach, especially when using <a href="http://graphql.org/">GraphQL</a>. It’s how we have built <a href="https://www.chromatic.com/?utm_source=storybook_website&utm_medium=link&utm_campaign=storybook">Chromatic</a> alongside 800+ stories.

💡 As an aside, passing data down the hierarchy is a legitimate approach, especially when using [GraphQL](http://graphql.org/). It’s how we have built [Chromatic](https://www.chromatic.com/?utm_source=storybook_website&utm_medium=link&utm_campaign=storybook) alongside 800+ stories.

</div>

## Supplying context with decorators
Expand Down Expand Up @@ -352,15 +354,15 @@ Cycling through states in Storybook makes it easy to test we’ve done this corr
/>
</video>

## Interaction tests
## Component tests

So far, we've been able to build a fully functional application from the ground up, starting from a simple component up to a screen and continuously testing each change using our stories. But each new story also requires a manual check on all the other stories to ensure the UI doesn't break. That's a lot of extra work.

Can't we automate this workflow and test our component interactions automatically?

### Write an interaction test using the play function
### Write a component test using the play function

Storybook's [`play`](https://storybook.js.org/docs/angular/writing-stories/play-function) and [`@storybook/addon-interactions`](https://storybook.js.org/docs/angular/writing-tests/interaction-testing) help us with that. A play function includes small snippets of code that run after the story renders.
Storybook's [`play`](https://storybook.js.org/docs/angular/writing-stories/play-function) and [`@storybook/addon-interactions`](https://storybook.js.org/docs/writing-tests/component-testing) help us with that. A play function includes small snippets of code that run after the story renders.

The play function helps us verify what happens to the UI when tasks are updated. It uses framework-agnostic DOM APIs, which means we can write stories with the play function to interact with the UI and simulate human behavior no matter the frontend framework.

Expand Down Expand Up @@ -441,9 +443,9 @@ Check your newly-created story. Click the `Interactions` panel to see the list o

With Storybook's play function, we were able to sidestep our problem, allowing us to interact with our UI and quickly check how it responds if we update our tasks—keeping the UI consistent at no extra manual effort.

But, if we take a closer look at our Storybook, we can see that it only runs the interaction tests when viewing the story. Therefore, we'd still have to go through each story to run all checks if we make a change. Couldn't we automate it?
But, if we take a closer look at our Storybook, we can see that it only runs the component tests when viewing the story. Therefore, we'd still have to go through each story to run all checks if we make a change. Couldn't we automate it?

The good news is that we can! Storybook's [test runner](https://storybook.js.org/docs/angular/writing-tests/test-runner) allows us to do just that. It's a standalone utility—powered by [Playwright](https://playwright.dev/)—that runs all our interactions tests and catches broken stories.
The good news is that we can! Storybook's [test runner](https://storybook.js.org/docs/writing-tests/test-runner) allows us to do just that. It's a standalone utility—powered by [Playwright](https://playwright.dev/)—that runs all our interactions tests and catches broken stories.

Let's see how it works! Run the following command to install it:

Expand All @@ -464,13 +466,15 @@ Next, update your `package.json` `scripts` and add a new test task:
Finally, with your Storybook running, open up a new terminal window and run the following command:

```shell
npm run test-storybook -- --watch
npm run test-storybook -- --url http://localhost:6006/ -- --watch
```

<div class="aside">
💡 Interaction testing with the play function is a fantastic way to test your UI components. It can do much more than we've seen here; we recommend reading the <a href="https://storybook.js.org/docs/angular/writing-tests/interaction-testing">official documentation</a> to learn more about it.
<br />
For an even deeper dive into testing, check out the <a href="/ui-testing-handbook">Testing Handbook</a>. It covers testing strategies used by scaled-front-end teams to supercharge your development workflow.

💡 Component testing with the play function is a fantastic way to test your UI components. It can do much more than we've seen here; we recommend reading the [official documentation](https://storybook.js.org/docs/writing-tests/component-testing) to learn more about it.

For an even deeper dive into testing, check out the [Testing Handbook](/ui-testing-handbook). It covers testing strategies used by scaled-front-end teams to supercharge your development workflow.

</div>

![Storybook test runner successfully runs all tests](/intro-to-storybook/storybook-test-runner-execution.png)
Expand Down
44 changes: 16 additions & 28 deletions content/intro-to-storybook/angular/en/simple-component.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,30 +66,24 @@ Below we build out Task’s three test states in the story file:
```ts:title=src/app/components/task.stories.ts
import type { Meta, StoryObj } from '@storybook/angular';

import { argsToTemplate } from '@storybook/angular';

import { action } from '@storybook/addon-actions';
import { fn } from '@storybook/test';

import TaskComponent from './task.component';

export const actionsData = {
onPinTask: action('onPinTask'),
onArchiveTask: action('onArchiveTask'),
export const ActionsData = {
onArchiveTask: fn(),
onPinTask: fn(),
};

const meta: Meta<TaskComponent> = {
title: 'Task',
component: TaskComponent,
//👇 Our exports that end in "Data" are not stories.
excludeStories: /.*Data$/,
tags: ['autodocs'],
render: (args: TaskComponent) => ({
props: {
...args,
onPinTask: actionsData.onPinTask,
onArchiveTask: actionsData.onArchiveTask,
},
template: `<app-task ${argsToTemplate(args)}></app-task>`,
}),
args: {
...ActionsData,
},
};

export default meta;
Expand Down Expand Up @@ -125,7 +119,9 @@ export const Archived: Story = {
```

<div class="aside">
💡 <a href="https://storybook.js.org/docs/angular/essentials/actions"><b>Actions</b></a> help you verify interactions when building UI components in isolation. Oftentimes you won't have access to the functions and state you have in context of the app. Use <code>action()</code> to stub them in.

💡 [**Actions**](https://storybook.js.org/docs/essentials/actions) help you verify interactions when building UI components in isolation. Oftentimes you won't have access to the functions and state you have in context of the app. Use `fn()` to stub them in.

</div>

There are two basic levels of organization in Storybook: the component and its child stories. Think of each story as a permutation of a component. You can have as many stories per component as you need.
Expand All @@ -141,16 +137,15 @@ To tell Storybook about the component we are documenting, we create a `default`
- `title` -- how to group or categorize the component in the Storybook sidebar
- `tags` -- to automatically generate documentation for our components
- `excludeStories`-- additional information required by the story but should not be rendered in Storybook
- `render` -- a custom [render function](https://storybook.js.org/docs/angular/api/csf#custom-render-functions) that allows us how the component is rendered in Storybook
- `argsToTemplate` -- a helper function that converts the args to property and event bindings for the component, providing robust workflow support for Storybook's controls and the component's inputs and outputs
- `args` -- define the action [args](https://storybook.js.org/docs/essentials/actions#action-args) that the component expects to mock out the custom events

To define our stories, we'll use Component Story Format 3 (also known as [CSF3](https://storybook.js.org/docs/angular/api/csf) ) to build out each of our test cases. This format is designed to build out each of our test cases in a concise way. By exporting an object containing each component state, we can define our tests more intuitively and author and reuse stories more efficiently.
To define our stories, we'll use Component Story Format 3 (also known as [CSF3](https://storybook.js.org/docs/api/csf) ) to build out each of our test cases. This format is designed to build out each of our test cases in a concise way. By exporting an object containing each component state, we can define our tests more intuitively and author and reuse stories more efficiently.

Arguments or [`args`](https://storybook.js.org/docs/angular/writing-stories/args) for short, allow us to live-edit our components with the controls addon without restarting Storybook. Once an [`args`](https://storybook.js.org/docs/angular/writing-stories/args) value changes, so does the component.
Arguments or [`args`](https://storybook.js.org/docs/writing-stories/args) for short, allow us to live-edit our components with the controls addon without restarting Storybook. Once an [`args`](https://storybook.js.org/docs/writing-stories/args) value changes, so does the component.

`action()` allows us to create a callback that appears in the **actions** panel of the Storybook UI when clicked. So when we build a pin button, we’ll be able to determine if a button click is successful in the UI.
`fn()` allows us to create a callback that appears in the **Actions** panel of the Storybook UI when clicked. So when we build a pin button, we’ll be able to determine if a button click is successful in the UI.

As we need to pass the same set of actions to all permutations of our component, it is convenient to bundle them up into a single `actionsData` variable and pass them into our story definition each time. Another nice thing about bundling the `actionsData` that a component needs is that you can `export` them and use them in stories for components that reuse this component, as we'll see later.
As we need to pass the same set of actions to all permutations of our component, it is convenient to bundle them up into a single `ActionsData` variable and pass them into our story definition each time. Another nice thing about bundling the `ActionsData` that a component needs is that you can `export` them and use them in stories for components that reuse this component, as we'll see later.

When creating a story, we use a base `task` arg to build out the shape of the task the component expects. Typically modeled from what the actual data looks like. Again, `export`-ing this shape will enable us to reuse it in later stories, as we'll see.

Expand All @@ -172,9 +167,6 @@ const config: StorybookConfig = {
name: '@storybook/angular',
options: {},
},
docs: {
autodocs: 'tag',
},
};
export default config;
```
Expand Down Expand Up @@ -331,12 +323,8 @@ const config: StorybookConfig = {
name: '@storybook/angular',
options: {},
},
docs: {
autodocs: 'tag',
},
};
export default config;

```

Finally, restart your Storybook to see the new addon enabled in the UI.
Expand Down
2 changes: 1 addition & 1 deletion content/intro-to-storybook/angular/en/test.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ No Storybook tutorial would be complete without testing. Testing is essential to

- **Accessibility tests** with a11y addon verify that the component is accessible to everyone. They're great for allowing us to collect information about how people with certain types of disabilities use our components.

- **Interaction tests** with the play function verify that the component behaves as expected when interacting with it. They're great for testing the behavior of a component when it's in use.
- **Component tests** with the play function verify that the component behaves as expected when interacting with it. They're great for testing the behavior of a component when it's in use.

## “But does it look right?”

Expand Down
Loading

0 comments on commit e0a2d2b

Please sign in to comment.