[BEEEP] [PM-10132] Upgrade storybook to v8 (#10288)

Upgrade storybook to version v8 which is a major upgrade. Storybook provides an
upgrade wizard which did most of the work.

- Ran npx storybook upgrade.
- Manually updated `remark-gfm` since the newer mdx requires v 4.
- Migrated all old stories still using `Story` to `StoryObj`.
This commit is contained in:
Oscar Hinton 2024-08-16 09:28:29 +02:00 committed by GitHub
parent 92f87dad9a
commit 604e22334a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 3875 additions and 8220 deletions

View File

@ -1,3 +1,4 @@
import { dirname, join } from "path";
import { StorybookConfig } from "@storybook/angular";
import TsconfigPathsPlugin from "tsconfig-paths-webpack-plugin";
import remarkGfm from "remark-gfm";
@ -20,11 +21,11 @@ const config: StorybookConfig = {
"../bitwarden_license/bit-web/src/**/*.stories.@(js|jsx|ts|tsx)",
],
addons: [
"@storybook/addon-links",
"@storybook/addon-essentials",
"@storybook/addon-a11y",
"@storybook/addon-designs",
"@storybook/addon-interactions",
getAbsolutePath("@storybook/addon-links"),
getAbsolutePath("@storybook/addon-essentials"),
getAbsolutePath("@storybook/addon-a11y"),
getAbsolutePath("@storybook/addon-designs"),
getAbsolutePath("@storybook/addon-interactions"),
{
name: "@storybook/addon-docs",
options: {
@ -37,7 +38,7 @@ const config: StorybookConfig = {
},
],
framework: {
name: "@storybook/angular",
name: getAbsolutePath("@storybook/angular"),
options: {},
},
core: {
@ -53,9 +54,12 @@ const config: StorybookConfig = {
}
return config;
},
docs: {
autodocs: true,
},
docs: {},
};
export default config;
// Recommended for mono-repositories
function getAbsolutePath(value: string): any {
return dirname(require.resolve(join(value, "package.json")));
}

View File

@ -1,4 +1,4 @@
import { addons } from "@storybook/addons";
import { addons } from "@storybook/manager-api";
import { create } from "@storybook/theming/create";
const lightTheme = create({

View File

@ -92,7 +92,6 @@ const preview: Preview = {
},
},
parameters: {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
@ -107,6 +106,7 @@ const preview: Preview = {
},
docs: { source: { type: "dynamic", excludeDecorators: true } },
},
tags: ["autodocs"],
};
export default preview;

View File

@ -1,7 +1,7 @@
import { importProvidersFrom } from "@angular/core";
import { FormBuilder, FormsModule, ReactiveFormsModule } from "@angular/forms";
import { action } from "@storybook/addon-actions";
import { applicationConfig, Meta, moduleMetadata, Story } from "@storybook/angular";
import { applicationConfig, Meta, moduleMetadata, StoryObj } from "@storybook/angular";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import {
@ -55,6 +55,9 @@ export default {
},
} as Meta;
// TODO: This is a workaround since this story does weird things.
type Story = StoryObj<any>;
const actionsData = {
onValueChanged: action("onValueChanged"),
onSubmit: action("onSubmit"),
@ -99,9 +102,8 @@ const itemsFactory = (n: number, type: AccessItemType) => {
const sampleMembers = itemsFactory(10, AccessItemType.Member);
const sampleGroups = itemsFactory(6, AccessItemType.Group);
const StandaloneAccessSelectorTemplate: Story<AccessSelectorComponent> = (
args: AccessSelectorComponent,
) => ({
// TODO: These renders are badly handled but storybook has made it more difficult to use multiple renders in a single story.
const StandaloneAccessSelectorRender = (args: any) => ({
props: {
items: [],
valueChanged: actionsData.onValueChanged,
@ -122,12 +124,10 @@ const StandaloneAccessSelectorTemplate: Story<AccessSelectorComponent> = (
[permissionMode]="permissionMode"
[showMemberRoles]="showMemberRoles"
></bit-access-selector>
`,
`,
});
const DialogAccessSelectorTemplate: Story<AccessSelectorComponent> = (
args: AccessSelectorComponent,
) => ({
const DialogAccessSelectorRender = (args: any) => ({
props: {
items: [],
valueChanged: actionsData.onValueChanged,
@ -164,7 +164,7 @@ const DialogAccessSelectorTemplate: Story<AccessSelectorComponent> = (
aria-label="Delete"></button>
</ng-container>
</bit-dialog>
`,
`,
});
const dialogAccessItems = itemsFactory(10, AccessItemType.Collection);
@ -190,8 +190,8 @@ const memberCollectionAccessItems = itemsFactory(3, AccessItemType.Collection).c
},
]);
export const Dialog = DialogAccessSelectorTemplate.bind({});
Dialog.args = {
export const Dialog: Story = {
args: {
permissionMode: "edit",
showMemberRoles: false,
showGroupColumn: true,
@ -200,21 +200,14 @@ Dialog.args = {
selectorHelpText: "Some helper text describing what this does",
emptySelectionText: "No collections added",
disabled: false,
initialValue: [],
initialValue: [] as any[],
items: dialogAccessItems,
};
Dialog.story = {
parameters: {
docs: {
storyDescription: `
Example of an access selector for modifying the collections a member has access to inside of a dialog.
`,
},
},
render: DialogAccessSelectorRender,
};
export const MemberCollectionAccess = StandaloneAccessSelectorTemplate.bind({});
MemberCollectionAccess.args = {
export const MemberCollectionAccess: Story = {
args: {
permissionMode: "edit",
showMemberRoles: false,
showGroupColumn: true,
@ -225,20 +218,12 @@ MemberCollectionAccess.args = {
disabled: false,
initialValue: [],
items: memberCollectionAccessItems,
};
MemberCollectionAccess.story = {
parameters: {
docs: {
storyDescription: `
Example of an access selector for modifying the collections a member has access to.
Includes examples of a readonly group and member that cannot be edited.
`,
},
},
render: StandaloneAccessSelectorRender,
};
export const MemberGroupAccess = StandaloneAccessSelectorTemplate.bind({});
MemberGroupAccess.args = {
export const MemberGroupAccess: Story = {
args: {
permissionMode: "readonly",
showMemberRoles: false,
columnHeader: "Groups",
@ -255,19 +240,12 @@ MemberGroupAccess.args = {
labelName: "Admin Group",
},
]),
};
MemberGroupAccess.story = {
parameters: {
docs: {
storyDescription: `
Example of an access selector for selecting which groups an individual member belongs too.
`,
},
},
render: StandaloneAccessSelectorRender,
};
export const GroupMembersAccess = StandaloneAccessSelectorTemplate.bind({});
GroupMembersAccess.args = {
export const GroupMembersAccess: Story = {
args: {
permissionMode: "hidden",
showMemberRoles: true,
columnHeader: "Members",
@ -277,19 +255,12 @@ GroupMembersAccess.args = {
disabled: false,
initialValue: [{ id: "2m" }, { id: "0m" }],
items: sampleMembers,
};
GroupMembersAccess.story = {
parameters: {
docs: {
storyDescription: `
Example of an access selector for selecting which members belong to an specific group.
`,
},
},
render: StandaloneAccessSelectorRender,
};
export const CollectionAccess = StandaloneAccessSelectorTemplate.bind({});
CollectionAccess.args = {
export const CollectionAccess: Story = {
args: {
permissionMode: "edit",
showMemberRoles: false,
columnHeader: "Groups/Members",
@ -321,22 +292,13 @@ CollectionAccess.args = {
readonly: true,
},
]),
};
GroupMembersAccess.story = {
parameters: {
docs: {
storyDescription: `
Example of an access selector for selecting which members/groups have access to a specific collection.
`,
},
},
render: StandaloneAccessSelectorRender,
};
const fb = new FormBuilder();
const ReactiveFormAccessSelectorTemplate: Story<AccessSelectorComponent> = (
args: AccessSelectorComponent,
) => ({
const ReactiveFormAccessSelectorRender = (args: any) => ({
props: {
items: [],
onSubmit: actionsData.onSubmit,
@ -359,8 +321,8 @@ const ReactiveFormAccessSelectorTemplate: Story<AccessSelectorComponent> = (
`,
});
export const ReactiveForm = ReactiveFormAccessSelectorTemplate.bind({});
ReactiveForm.args = {
export const ReactiveForm: Story = {
args: {
formObj: fb.group({ formItems: [[{ id: "1g" }]] }),
permissionMode: "edit",
showMemberRoles: false,
@ -370,4 +332,6 @@ ReactiveForm.args = {
"Permissions set for a member will replace permissions set by that member's group",
emptySelectionText: "No members or groups added",
items: sampleGroups.concat(sampleMembers),
},
render: ReactiveFormAccessSelectorRender,
};

View File

@ -1,6 +1,6 @@
import { importProvidersFrom } from "@angular/core";
import { RouterModule } from "@angular/router";
import { Meta, Story, applicationConfig, moduleMetadata } from "@storybook/angular";
import { Meta, StoryObj, applicationConfig, moduleMetadata } from "@storybook/angular";
import { delay, of, startWith } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
@ -26,9 +26,7 @@ export default {
],
}),
],
} as Meta;
const Template: Story = (args) => ({
render: (args) => ({
props: {
createServiceAccount: false,
importSecrets$: of(false),
@ -64,22 +62,25 @@ const Template: Story = (args) => ({
></app-onboarding-task>
</app-onboarding>
`,
});
}),
} as Meta;
export const Empty = Template.bind({});
type Story = StoryObj<OnboardingComponent>;
export const Partial = Template.bind({});
Partial.args = {
...Template.args,
export const Empty: Story = {};
export const Partial = {
args: {
createServiceAccount: true,
createProject: true,
},
};
export const Full = Template.bind({});
Full.args = {
...Template.args,
export const Full = {
args: {
createServiceAccount: true,
createProject: true,
createSecret: true,
importSecrets$: of(true).pipe(delay(0), startWith(false)),
},
};

View File

@ -1,6 +1,6 @@
import { importProvidersFrom } from "@angular/core";
import { RouterTestingModule } from "@angular/router/testing";
import { Meta, Story, applicationConfig, moduleMetadata } from "@storybook/angular";
import { Meta, StoryObj, applicationConfig, moduleMetadata } from "@storybook/angular";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { BadgeModule, IconModule } from "@bitwarden/components";
@ -32,18 +32,18 @@ export default {
},
} as Meta;
const Template: Story<ReportCardComponent> = (args: ReportCardComponent) => ({
props: args,
});
type Story = StoryObj<ReportCardComponent>;
export const Enabled = Template.bind({});
export const Enabled: Story = {};
export const RequiresPremium = Template.bind({});
RequiresPremium.args = {
export const RequiresPremium: Story = {
args: {
variant: ReportVariant.RequiresPremium,
},
};
export const RequiresUpgrade = Template.bind({});
RequiresUpgrade.args = {
export const RequiresUpgrade: Story = {
args: {
variant: ReportVariant.RequiresUpgrade,
},
};

View File

@ -1,6 +1,6 @@
import { importProvidersFrom } from "@angular/core";
import { RouterTestingModule } from "@angular/router/testing";
import { Meta, Story, applicationConfig, moduleMetadata } from "@storybook/angular";
import { Meta, StoryObj, applicationConfig, moduleMetadata } from "@storybook/angular";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { BadgeModule, IconModule } from "@bitwarden/components";
@ -34,8 +34,6 @@ export default {
},
} as Meta;
const Template: Story<ReportListComponent> = (args: ReportListComponent) => ({
props: args,
});
type Story = StoryObj<ReportListComponent>;
export const Default = Template.bind({});
export const Default: Story = {};

View File

@ -1,4 +1,4 @@
import { Meta, moduleMetadata, Story } from "@storybook/angular";
import { Meta, moduleMetadata, StoryObj } from "@storybook/angular";
import { of } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
@ -47,9 +47,6 @@ export default {
],
} as Meta;
const Template: Story<PremiumBadgeComponent> = (args: PremiumBadgeComponent) => ({
props: args,
});
type Story = StoryObj<PremiumBadgeComponent>;
export const Primary = Template.bind({});
Primary.args = {};
export const Primary: Story = {};

View File

@ -1,6 +1,6 @@
import { importProvidersFrom } from "@angular/core";
import { RouterModule } from "@angular/router";
import { applicationConfig, Meta, moduleMetadata, Story } from "@storybook/angular";
import { applicationConfig, Meta, moduleMetadata, StoryObj } from "@storybook/angular";
import { BehaviorSubject, of } from "rxjs";
import { OrganizationUserType } from "@bitwarden/common/admin-console/enums";
@ -118,12 +118,10 @@ export default {
argTypes: { onEvent: { action: "onEvent" } },
} as Meta;
const Template: Story<VaultItemsComponent> = (args: VaultItemsComponent) => ({
props: args,
});
type Story = StoryObj<VaultItemsComponent>;
export const Individual = Template.bind({});
Individual.args = {
export const Individual: Story = {
args: {
ciphers,
collections: [],
showOwner: true,
@ -133,11 +131,11 @@ Individual.args = {
showBulkMove: true,
showBulkTrashOptions: false,
useEvents: false,
cloneableOrganizationCiphers: false,
},
};
export const IndividualDisabled = Template.bind({});
IndividualDisabled.args = {
export const IndividualDisabled: Story = {
args: {
ciphers,
collections: [],
disabled: true,
@ -148,11 +146,11 @@ IndividualDisabled.args = {
showBulkMove: true,
showBulkTrashOptions: false,
useEvents: false,
cloneableOrganizationCiphers: false,
},
};
export const IndividualTrash = Template.bind({});
IndividualTrash.args = {
export const IndividualTrash: Story = {
args: {
ciphers: deletedCiphers,
collections: [],
showOwner: true,
@ -162,11 +160,11 @@ IndividualTrash.args = {
showBulkMove: false,
showBulkTrashOptions: true,
useEvents: false,
cloneableOrganizationCiphers: false,
},
};
export const IndividualTopLevelCollection = Template.bind({});
IndividualTopLevelCollection.args = {
export const IndividualTopLevelCollection: Story = {
args: {
ciphers: [],
collections,
showOwner: true,
@ -176,11 +174,11 @@ IndividualTopLevelCollection.args = {
showBulkMove: false,
showBulkTrashOptions: false,
useEvents: false,
cloneableOrganizationCiphers: false,
},
};
export const IndividualSecondLevelCollection = Template.bind({});
IndividualSecondLevelCollection.args = {
export const IndividualSecondLevelCollection: Story = {
args: {
ciphers,
collections,
showOwner: true,
@ -190,11 +188,11 @@ IndividualSecondLevelCollection.args = {
showBulkMove: true,
showBulkTrashOptions: false,
useEvents: false,
cloneableOrganizationCiphers: false,
},
};
export const OrganizationVault = Template.bind({});
OrganizationVault.args = {
export const OrganizationVault: Story = {
args: {
ciphers: organizationOnlyCiphers,
collections: [],
showOwner: false,
@ -204,11 +202,11 @@ OrganizationVault.args = {
showBulkMove: false,
showBulkTrashOptions: false,
useEvents: true,
cloneableOrganizationCiphers: true,
},
};
export const OrganizationTrash = Template.bind({});
OrganizationTrash.args = {
export const OrganizationTrash: Story = {
args: {
ciphers: deletedOrganizationOnlyCiphers,
collections: [],
showOwner: false,
@ -218,14 +216,14 @@ OrganizationTrash.args = {
showBulkMove: false,
showBulkTrashOptions: true,
useEvents: true,
cloneableOrganizationCiphers: true,
},
};
const unassignedCollection = new CollectionAdminView();
unassignedCollection.id = Unassigned;
unassignedCollection.name = "Unassigned";
export const OrganizationTopLevelCollection = Template.bind({});
OrganizationTopLevelCollection.args = {
export const OrganizationTopLevelCollection: Story = {
args: {
ciphers: [],
collections: collections.concat(unassignedCollection),
showOwner: false,
@ -235,11 +233,11 @@ OrganizationTopLevelCollection.args = {
showBulkMove: false,
showBulkTrashOptions: false,
useEvents: true,
cloneableOrganizationCiphers: true,
},
};
export const OrganizationSecondLevelCollection = Template.bind({});
OrganizationSecondLevelCollection.args = {
export const OrganizationSecondLevelCollection: Story = {
args: {
ciphers: organizationOnlyCiphers,
collections,
showOwner: false,
@ -249,7 +247,7 @@ OrganizationSecondLevelCollection.args = {
showBulkMove: false,
showBulkTrashOptions: false,
useEvents: true,
cloneableOrganizationCiphers: true,
},
};
function createCipherView(i: number, deleted = false): CipherView {

View File

@ -48,7 +48,7 @@ export const Premium: Story = {
args: {
bannerType: "premium",
},
render: (args: BannerComponent) => ({
render: (args) => ({
props: args,
template: `
<bit-banner [bannerType]="bannerType" (onClose)="onClose($event)" [showClose]=showClose>
@ -93,7 +93,7 @@ export const HideClose: Story = {
export const Stacked: Story = {
args: {},
render: (args: BannerComponent) => ({
render: (args) => ({
props: args,
template: `
<bit-banner bannerType="premium" (onClose)="onClose($event)">

View File

@ -1,6 +1,6 @@
import { FormsModule } from "@angular/forms";
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
import { getAllByRole, userEvent } from "@storybook/testing-library";
import { getAllByRole, userEvent } from "@storybook/test";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";

View File

@ -1,6 +1,6 @@
import { RouterTestingModule } from "@angular/router/testing";
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
import { userEvent } from "@storybook/testing-library";
import { userEvent } from "@storybook/test";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";

View File

@ -69,8 +69,7 @@ export const WithoutIcon: Story = {
};
export const WithoutRoute: Story = {
render: (args: NavItemComponent) => ({
props: args,
render: () => ({
template: `
<bit-nav-item text="Hello World" icon="bwi-collection"></bit-nav-item>
`,
@ -78,7 +77,7 @@ export const WithoutRoute: Story = {
};
export const WithChildButtons: Story = {
render: (args: NavItemComponent) => ({
render: (args) => ({
props: args,
template: `
<bit-nav-item text="Hello World" [route]="['']" icon="bwi-collection">
@ -104,7 +103,7 @@ export const WithChildButtons: Story = {
};
export const MultipleItemsWithDivider: Story = {
render: (args: NavItemComponent) => ({
render: (args) => ({
props: args,
template: `
<bit-nav-item text="Hello World" icon="bwi-collection"></bit-nav-item>
@ -117,7 +116,7 @@ export const MultipleItemsWithDivider: Story = {
};
export const ForceActiveStyles: Story = {
render: (args: NavItemComponent) => ({
render: (args) => ({
props: args,
template: `
<bit-nav-item text="First Nav" icon="bwi-collection"></bit-nav-item>

View File

@ -1,5 +1,3 @@
{/* Iconography.stories.mdx */}
import { Meta } from "@storybook/addon-docs";
<Meta title="Documentation/Icons" />

View File

@ -8,13 +8,7 @@ import {
componentWrapperDecorator,
moduleMetadata,
} from "@storybook/angular";
import {
userEvent,
getAllByRole,
getByRole,
getByLabelText,
fireEvent,
} from "@storybook/testing-library";
import { userEvent, getAllByRole, getByRole, getByLabelText, fireEvent } from "@storybook/test";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@ -126,14 +120,14 @@ export const MenuOpen: Story = {
export const DefaultDialogOpen: Story = {
...Default,
play: (context) => {
play: async (context) => {
const canvas = context.canvasElement;
const dialogButton = getByRole(canvas, "button", {
name: "Open Dialog",
});
// workaround for userEvent not firing in FF https://github.com/testing-library/user-event/issues/1075
fireEvent.click(dialogButton);
await fireEvent.click(dialogButton);
},
};
@ -151,14 +145,14 @@ export const PopoverOpen: Story = {
export const SimpleDialogOpen: Story = {
...Default,
play: (context) => {
play: async (context) => {
const canvas = context.canvasElement;
const submitButton = getByRole(canvas, "button", {
name: "Submit",
});
// workaround for userEvent not firing in FF https://github.com/testing-library/user-event/issues/1075
fireEvent.click(submitButton);
await fireEvent.click(submitButton);
},
};

View File

@ -103,7 +103,7 @@ export const ContentTabs: Story = {
};
export const NavigationTabs: Story = {
render: (args: TabGroupComponent) => ({
render: (args) => ({
props: args,
template: `
<bit-tab-nav-bar label="Main">
@ -126,7 +126,7 @@ export const NavigationTabs: Story = {
};
export const PreserveContentTabs: Story = {
render: (args: any) => ({
render: (args) => ({
props: args,
template: `
<bit-tab-group label="Preserve Content Tabs" [preserveContent]="true" class="tw-text-main">
@ -147,7 +147,7 @@ export const PreserveContentTabs: Story = {
};
export const KeyboardNavigation: Story = {
render: (args: any) => ({
render: (args) => ({
props: args,
template: `
<bit-tab-group label="Keyboard Navigation Tabs" class="tw-text-main">

11320
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -47,15 +47,15 @@
"@electron/notarize": "2.4.0",
"@electron/rebuild": "3.6.0",
"@ngtools/webpack": "16.2.14",
"@storybook/addon-a11y": "7.6.19",
"@storybook/addon-actions": "7.6.19",
"@storybook/addon-designs": "7.0.9",
"@storybook/addon-essentials": "7.6.19",
"@storybook/addon-interactions": "7.6.19",
"@storybook/addon-links": "7.6.19",
"@storybook/angular": "7.6.19",
"@storybook/jest": "0.2.3",
"@storybook/testing-library": "0.2.2",
"@storybook/addon-a11y": "8.2.6",
"@storybook/addon-actions": "8.2.6",
"@storybook/addon-designs": "8.0.3",
"@storybook/addon-essentials": "8.2.6",
"@storybook/addon-interactions": "8.2.6",
"@storybook/addon-links": "8.2.6",
"@storybook/angular": "8.2.6",
"@storybook/manager-api": "8.2.6",
"@storybook/theming": "8.2.6",
"@types/argon2-browser": "1.18.4",
"@types/chrome": "0.0.262",
"@types/firefox-webext-browser": "111.0.5",
@ -76,7 +76,6 @@
"@types/node-ipc": "9.2.3",
"@types/papaparse": "5.3.14",
"@types/proper-lockfile": "4.1.4",
"@types/react": "16.14.60",
"@types/retry": "0.12.5",
"@types/zxcvbn": "4.4.4",
"@typescript-eslint/eslint-plugin": "7.16.1",
@ -127,14 +126,12 @@
"prettier": "3.3.3",
"prettier-plugin-tailwindcss": "0.6.5",
"process": "0.11.10",
"react": "18.3.1",
"react-dom": "18.3.1",
"regedit": "^3.0.3",
"remark-gfm": "3.0.1",
"remark-gfm": "4.0.0",
"rimraf": "6.0.1",
"sass": "1.74.1",
"sass-loader": "14.2.1",
"storybook": "7.6.19",
"storybook": "8.2.6",
"style-loader": "3.3.4",
"tailwindcss": "3.4.3",
"ts-jest": "29.2.2",
@ -214,9 +211,6 @@
},
"replacestream": "4.0.3"
},
"resolutions": {
"@types/react": "16.14.60"
},
"lint-staged": {
"*": "prettier --cache --ignore-unknown --write",
"*.ts": "eslint --cache --cache-strategy content --fix"