Merge pull request 'run storybook in nix derivation' (#5589) from hgl-storybook into main

Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5589
This commit is contained in:
hgl
2025-10-23 07:23:26 +00:00
14 changed files with 380 additions and 361 deletions

View File

@@ -1,9 +1,11 @@
{ lib, ... }:
{ {
perSystem = perSystem =
{ {
self', self',
pkgs, pkgs,
config, config,
system,
... ...
}: }:
{ {
@@ -20,7 +22,6 @@
clan-ts-api = config.packages.clan-ts-api; clan-ts-api = config.packages.clan-ts-api;
fonts = config.packages.fonts; fonts = config.packages.fonts;
}; };
}; };
# // # //
# todo add darwin support # todo add darwin support
@@ -41,6 +42,11 @@
inherit (config.packages) clan-ts-api; inherit (config.packages) clan-ts-api;
}; };
checks = config.packages.clan-app.tests; checks =
config.packages.clan-app.tests
# Sandboxed Darwin nix build can't spawn a headless brwoser
// lib.optionalAttrs (!lib.hasSuffix "darwin" system) {
inherit (config.packages.clan-app-ui.tests) clan-app-ui-storybook;
};
}; };
} }

View File

@@ -12,11 +12,13 @@
fetchzip, fetchzip,
process-compose, process-compose,
json2ts, json2ts,
playwright-driver, playwright,
luakit, luakit,
jq,
self', self',
}: }:
let let
RED = "\\033[1;31m";
GREEN = "\\033[1;32m"; GREEN = "\\033[1;32m";
NC = "\\033[0m"; NC = "\\033[0m";
@@ -108,32 +110,39 @@ mkShell {
export PC_CONFIG_FILES="$CLAN_CORE_PATH/pkgs/clan-app/process-compose.yaml" export PC_CONFIG_FILES="$CLAN_CORE_PATH/pkgs/clan-app/process-compose.yaml"
echo -e "${GREEN}To launch a qemu VM for testing, run:\n start-vm <number of VMs>${NC}" echo -e "${GREEN}To launch a qemu VM for testing, run:\n start-vm <number of VMs>${NC}"
''
+
# todo darwin support needs some work
(lib.optionalString stdenv.hostPlatform.isLinux ''
# configure playwright for storybook snapshot testing
# we only want webkit as that matches what the app is rendered with
# configure playwright for storybook snapshot testing
# we only want webkit as that matches what the app is rendered with
playwright_ver=$(${jq}/bin/jq --raw-output .devDependencies.playwright ${./ui/package.json})
if [[ $playwright_ver != '${playwright.version}' ]]; then
echo >&2 -en '${RED}'
echo >&2 "Error: playwright npm package version ($playwright_ver) is different from that from the nixpkgs (${playwright.version})"
echo >&2 "Run this command to update the npm package version"
echo >&2
echo >&2 " npm i -D --save-exact playwright@${playwright.version}"
echo >&2
echo >&2 -en '${NC}'
else
export PLAYWRIGHT_BROWSERS_PATH=${ export PLAYWRIGHT_BROWSERS_PATH=${
playwright-driver.browsers.override { playwright.browsers.override {
withFfmpeg = false; withFfmpeg = false;
withFirefox = false; withFirefox = true;
withWebkit = true; withWebkit = false;
withChromium = false; withChromium = false;
withChromiumHeadlessShell = false; withChromiumHeadlessShell = false;
} }
} }
# This is needed to disable revisionOverrides in browsers.json which
# the playwright nix package does not support
# https://github.com/NixOS/nixpkgs/blob/f9c3b27aa3f9caac6717973abcc549dbde16bdd4/pkgs/development/web/playwright/driver.nix#L261
export PLAYWRIGHT_HOST_PLATFORM_OVERRIDE=nixos
# stop playwright from trying to validate it has downloaded the necessary browsers # stop playwright from trying to validate it has downloaded the necessary browsers
# we are providing them manually via nix # we are providing them manually via nix
export PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS=1
export PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS=true fi
'';
# playwright browser drivers are versioned e.g. webkit-2191
# this helps us avoid having to update the playwright js dependency everytime we update nixpkgs and vice versa
# see vitest.config.js for corresponding launch configuration
export PLAYWRIGHT_WEBKIT_EXECUTABLE=$(find -L "$PLAYWRIGHT_BROWSERS_PATH" -type f -name "pw_run.sh")
'');
} }

View File

@@ -4,8 +4,11 @@
importNpmLock, importNpmLock,
clan-ts-api, clan-ts-api,
fonts, fonts,
ps,
jq,
playwright,
}: }:
buildNpmPackage (_finalAttrs: { buildNpmPackage (finalAttrs: {
pname = "clan-app-ui"; pname = "clan-app-ui";
version = "0.0.1"; version = "0.0.1";
nodejs = nodejs_22; nodejs = nodejs_22;
@@ -32,36 +35,53 @@ buildNpmPackage (_finalAttrs: {
# todo figure out why this fails only inside of Nix # todo figure out why this fails only inside of Nix
# Something about passing orientation in any of the Form stories is causing the browser to crash # Something about passing orientation in any of the Form stories is causing the browser to crash
# `npm run test-storybook-static` works fine in the devshell # `npm run test-storybook-static` works fine in the devshell
#
# passthru = rec { passthru = {
# storybook = buildNpmPackage { tests = {
# pname = "${finalAttrs.pname}-storybook"; "${finalAttrs.pname}-storybook" = buildNpmPackage {
# inherit (finalAttrs) pname = "${finalAttrs.pname}-storybook";
# version inherit (finalAttrs)
# nodejs version
# src nodejs
# npmDeps src
# npmConfigHook npmDeps
# preBuild npmConfigHook
# ; ;
#
# nativeBuildInputs = finalAttrs.nativeBuildInputs ++ [ nativeBuildInputs = finalAttrs.nativeBuildInputs ++ [
# ps ps
# ]; jq
# ];
# npmBuildScript = "test-storybook-static";
# npmBuildScript = "test-storybook";
# env = finalAttrs.env // {
# PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD = 1; env = {
# PLAYWRIGHT_BROWSERS_PATH = "${playwright-driver.browsers.override { PLAYWRIGHT_BROWSERS_PATH = "${playwright.browsers.override {
# withChromiumHeadlessShell = true; withFfmpeg = false;
# }}"; withFirefox = true;
# PLAYWRIGHT_HOST_PLATFORM_OVERRIDE = "ubuntu-24.04"; withWebkit = false;
# }; withChromium = false;
# withChromiumHeadlessShell = false;
# postBuild = '' }}";
# mv storybook-static $out PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS = true;
# ''; # This is needed to disable revisionOverrides in browsers.json which
# }; # the playwright nix package does not support:
# }; # https://github.com/NixOS/nixpkgs/blob/f9c3b27aa3f9caac6717973abcc549dbde16bdd4/pkgs/development/web/playwright/driver.nix#L261
PLAYWRIGHT_HOST_PLATFORM_OVERRIDE = "nixos";
DEBUG = "vitest:*";
};
preBuild = finalAttrs.preBuild + ''
playwright_ver=$(jq --raw-output .devDependencies.playwright ${./ui/package.json})
if [[ $playwright_ver != '${playwright.version}' ]]; then
echo >&2 "playwright npm package version ($playwright_ver) is different from that from the nixpkgs (${playwright.version})"
echo >&2 "Run this command to update the npm package version"
echo >&2
echo >&2 " npm i -D --save-exact playwright@${playwright.version}"
echo >&2
exit 1
fi
'';
};
};
};
}) })

View File

@@ -55,7 +55,7 @@
"jsdom": "^26.1.0", "jsdom": "^26.1.0",
"knip": "^5.61.2", "knip": "^5.61.2",
"markdown-to-jsx": "^7.7.10", "markdown-to-jsx": "^7.7.10",
"playwright": "~1.55.1", "playwright": "1.54.1",
"postcss": "^8.4.38", "postcss": "^8.4.38",
"postcss-url": "^10.1.3", "postcss-url": "^10.1.3",
"prettier": "^3.2.5", "prettier": "^3.2.5",
@@ -7292,13 +7292,13 @@
} }
}, },
"node_modules/playwright": { "node_modules/playwright": {
"version": "1.55.1", "version": "1.54.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.55.1.tgz", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.54.1.tgz",
"integrity": "sha512-cJW4Xd/G3v5ovXtJJ52MAOclqeac9S/aGGgRzLabuF8TnIb6xHvMzKIa6JmrRzUkeXJgfL1MhukP0NK6l39h3A==", "integrity": "sha512-peWpSwIBmSLi6aW2auvrUtf2DqY16YYcCMO8rTVx486jKmDTJg7UAhyrraP98GB8BoPURZP8+nxO7TSd4cPr5g==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"playwright-core": "1.55.1" "playwright-core": "1.54.1"
}, },
"bin": { "bin": {
"playwright": "cli.js" "playwright": "cli.js"
@@ -7311,9 +7311,9 @@
} }
}, },
"node_modules/playwright-core": { "node_modules/playwright-core": {
"version": "1.55.1", "version": "1.54.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.1.tgz", "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.54.1.tgz",
"integrity": "sha512-Z6Mh9mkwX+zxSlHqdr5AOcJnfp+xUWLCt9uKV18fhzA8eyxUd8NUWzAjxUh55RZKSYwDGX0cfaySdhZJGMoJ+w==", "integrity": "sha512-Nbjs2zjj0htNhzgiy5wu+3w09YetDx5pkrpI/kZotDlDUaYk0HVA5xrBVPdow4SAUIlhgKcJeJg4GRKW6xHusA==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"bin": { "bin": {

View File

@@ -14,11 +14,9 @@
"vite": "vite", "vite": "vite",
"storybook": "storybook", "storybook": "storybook",
"knip": "knip --fix", "knip": "knip --fix",
"storybook-build": "storybook build",
"storybook-dev": "storybook dev -p 6006", "storybook-dev": "storybook dev -p 6006",
"test-storybook": "vitest run --project storybook", "test-storybook": "vitest run --project storybook",
"test-storybook-update-snapshots": "vitest run --project storybook --update", "test-storybook-update-snapshots": "vitest run --project storybook --update"
"test-storybook-static": "npm run storybook-build && concurrently -k -s first -n 'SB,TEST' -c 'magenta,blue' 'http-server storybook-static --port 6006 --silent' 'wait-on tcp:127.0.0.1:6006 && npm run test-storybook'"
}, },
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
@@ -50,7 +48,7 @@
"jsdom": "^26.1.0", "jsdom": "^26.1.0",
"knip": "^5.61.2", "knip": "^5.61.2",
"markdown-to-jsx": "^7.7.10", "markdown-to-jsx": "^7.7.10",
"playwright": "~1.55.1", "playwright": "1.54.1",
"postcss": "^8.4.38", "postcss": "^8.4.38",
"postcss-url": "^10.1.3", "postcss-url": "^10.1.3",
"prettier": "^3.2.5", "prettier": "^3.2.5",

View File

@@ -1,7 +1,7 @@
import type { Meta, StoryObj } from "@kachurun/storybook-solid"; import type { Meta, StoryObj } from "@kachurun/storybook-solid";
import { Button, ButtonProps } from "./Button"; import { Button, ButtonProps } from "./Button";
import { Component } from "solid-js"; import { Component } from "solid-js";
import { expect, fn, waitFor, within } from "storybook/test"; import { expect, fn, within } from "storybook/test";
import { StoryContext } from "@kachurun/storybook-solid-vite"; import { StoryContext } from "@kachurun/storybook-solid-vite";
const getCursorStyle = (el: Element) => window.getComputedStyle(el).cursor; const getCursorStyle = (el: Element) => window.getComputedStyle(el).cursor;

View File

@@ -11,6 +11,59 @@ import { Button } from "../Button/Button";
const meta: Meta<ModalProps> = { const meta: Meta<ModalProps> = {
title: "Components/Modal", title: "Components/Modal",
component: Modal, component: Modal,
render: (args: ModalProps) => (
<Modal
{...args}
children={
<form class="flex flex-col gap-5">
<Fieldset legend="General">
{(props: FieldsetFieldProps) => (
<>
<TextInput
{...props}
label="First Name"
size="s"
required={true}
input={{ placeholder: "Ron" }}
/>
<TextInput
{...props}
label="Last Name"
size="s"
required={true}
input={{ placeholder: "Burgundy" }}
/>
<TextArea
{...props}
label="Bio"
size="s"
input={{
placeholder: "Tell us a bit about yourself",
rows: 8,
}}
/>
<Checkbox
{...props}
size="s"
label="Accept Terms"
required={true}
/>
</>
)}
</Fieldset>
<div class="flex w-full items-center justify-end gap-4">
<Button size="s" hierarchy="secondary" onClick={close}>
Cancel
</Button>
<Button size="s" type="submit" hierarchy="primary" onClick={close}>
Save
</Button>
</div>
</form>
}
/>
),
}; };
export default meta; export default meta;
@@ -21,50 +74,5 @@ export const Default: Story = {
args: { args: {
title: "Example Modal", title: "Example Modal",
onClose: fn(), onClose: fn(),
children: (
<form class="flex flex-col gap-5">
<Fieldset legend="General">
{(props: FieldsetFieldProps) => (
<>
<TextInput
{...props}
label="First Name"
size="s"
required={true}
input={{ placeholder: "Ron" }}
/>
<TextInput
{...props}
label="Last Name"
size="s"
required={true}
input={{ placeholder: "Burgundy" }}
/>
<TextArea
{...props}
label="Bio"
size="s"
input={{ placeholder: "Tell us a bit about yourself", rows: 8 }}
/>
<Checkbox
{...props}
size="s"
label="Accept Terms"
required={true}
/>
</>
)}
</Fieldset>
<div class="flex w-full items-center justify-end gap-4">
<Button size="s" hierarchy="secondary" onClick={close}>
Cancel
</Button>
<Button size="s" type="submit" hierarchy="primary" onClick={close}>
Save
</Button>
</div>
</form>
),
}, },
}; };

View File

@@ -7,11 +7,9 @@ import {
} from "@solidjs/router"; } from "@solidjs/router";
import { Sidebar } from "@/src/components/Sidebar/Sidebar"; import { Sidebar } from "@/src/components/Sidebar/Sidebar";
import { Suspense } from "solid-js"; import { Suspense } from "solid-js";
import { QueryClient, QueryClientProvider } from "@tanstack/solid-query";
import { addClanURI, resetStore } from "@/src/stores/clan"; import { addClanURI, resetStore } from "@/src/stores/clan";
import { SolidQueryDevtools } from "@tanstack/solid-query-devtools"; import { SolidQueryDevtools } from "@tanstack/solid-query-devtools";
import { encodeBase64 } from "@/src/hooks/clan"; import { encodeBase64 } from "@/src/hooks/clan";
import { ApiClientProvider } from "@/src/hooks/ApiClient";
import { import {
ApiCall, ApiCall,
OperationArgs, OperationArgs,

View File

@@ -14,6 +14,7 @@ import { splitProps } from "solid-js";
import { Typography } from "@/src/components/Typography/Typography"; import { Typography } from "@/src/components/Typography/Typography";
import { MachineTags } from "@/src/components/Form/MachineTags"; import { MachineTags } from "@/src/components/Form/MachineTags";
import { setValue } from "@modular-forms/solid"; import { setValue } from "@modular-forms/solid";
import { StoryContext } from "@kachurun/storybook-solid-vite";
type Story = StoryObj<SidebarPaneProps>; type Story = StoryObj<SidebarPaneProps>;
@@ -30,6 +31,13 @@ const profiles = {
const meta: Meta<SidebarPaneProps> = { const meta: Meta<SidebarPaneProps> = {
title: "Components/SidebarPane", title: "Components/SidebarPane",
component: SidebarPane, component: SidebarPane,
decorators: [
(
Story: StoryObj<SidebarPaneProps>,
context: StoryContext<SidebarPaneProps>,
) =>
() => <Story {...context.args} />,
],
}; };
export default meta; export default meta;
@@ -40,133 +48,140 @@ export const Default: Story = {
onClose: () => { onClose: () => {
console.log("closing"); console.log("closing");
}, },
children: ( },
<> // We have to provide children within a custom render function to ensure we aren't creating any reactivity outside the
<SidebarSectionForm // solid-js scope.
title="General" render: (args: SidebarPaneProps) => (
schema={v.object({ <SidebarPane
firstName: v.pipe( {...args}
v.string(), children={
v.nonEmpty("Please enter a first name."), <>
), <SidebarSectionForm
lastName: v.pipe( title="General"
v.string(), schema={v.object({
v.nonEmpty("Please enter a last name."), firstName: v.pipe(
), v.string(),
bio: v.string(), v.nonEmpty("Please enter a first name."),
shareProfile: v.optional(v.boolean()), ),
})} lastName: v.pipe(
initialValues={profiles.ron} v.string(),
onSubmit={async () => { v.nonEmpty("Please enter a last name."),
console.log("saving general"); ),
}} bio: v.string(),
> shareProfile: v.optional(v.boolean()),
{({ editing, Field }) => ( })}
<div class="flex flex-col gap-3"> initialValues={profiles.ron}
<Field name="firstName"> onSubmit={async () => {
{(field, input) => ( console.log("saving general");
<TextInput }}
{...field} >
size="s" {({ editing, Field }) => (
inverted <div class="flex flex-col gap-3">
label="First Name" <Field name="firstName">
value={field.value} {(field, input) => (
required <TextInput
readOnly={!editing} {...field}
orientation="horizontal"
input={input}
/>
)}
</Field>
<Divider />
<Field name="lastName">
{(field, input) => (
<TextInput
{...field}
size="s"
inverted
label="Last Name"
value={field.value}
required
readOnly={!editing}
orientation="horizontal"
input={input}
/>
)}
</Field>
<Divider />
<Field name="bio">
{(field, input) => (
<TextArea
{...field}
value={field.value}
size="s"
label="Bio"
inverted
readOnly={!editing}
orientation="horizontal"
input={{ ...input, rows: 4 }}
/>
)}
</Field>
<Field name="shareProfile" type="boolean">
{(field, input) => {
return (
<Checkbox
{...splitProps(field, ["value"])[1]}
defaultChecked={field.value}
size="s" size="s"
label="Share"
inverted inverted
label="First Name"
value={field.value}
required
readOnly={!editing} readOnly={!editing}
orientation="horizontal" orientation="horizontal"
input={input} input={input}
/> />
); )}
}} </Field>
</Field> <Divider />
</div> <Field name="lastName">
)} {(field, input) => (
</SidebarSectionForm> <TextInput
<SidebarSectionForm {...field}
title="Tags" size="s"
schema={v.object({ inverted
tags: v.pipe(v.array(v.string()), v.nonEmpty()), label="Last Name"
})} value={field.value}
initialValues={profiles.ron} required
onSubmit={async (values) => { readOnly={!editing}
console.log("saving tags", values); orientation="horizontal"
}} input={input}
> />
{({ editing, Field, formStore }) => ( )}
<Field name="tags" type="string[]"> </Field>
{(field, props) => ( <Divider />
<MachineTags <Field name="bio">
{...splitProps(field, ["value"])[1]} {(field, input) => (
size="s" <TextArea
onChange={(newVal) => { {...field}
// Workaround for now, until we manage to use native events value={field.value}
setValue(formStore, field.name, newVal); size="s"
label="Bio"
inverted
readOnly={!editing}
orientation="horizontal"
input={{ ...input, rows: 4 }}
/>
)}
</Field>
<Field name="shareProfile" type="boolean">
{(field, input) => {
return (
<Checkbox
{...splitProps(field, ["value"])[1]}
defaultChecked={field.value}
size="s"
label="Share"
inverted
readOnly={!editing}
orientation="horizontal"
input={input}
/>
);
}} }}
inverted </Field>
required </div>
readOnly={!editing} )}
orientation="horizontal" </SidebarSectionForm>
defaultValue={field.value} <SidebarSectionForm
/> title="Tags"
)} schema={v.object({
</Field> tags: v.pipe(v.array(v.string()), v.nonEmpty()),
)} })}
</SidebarSectionForm> initialValues={profiles.ron}
<SidebarSection title="Simple"> onSubmit={async (values) => {
<Typography tag="h2" hierarchy="title" size="m" inverted> console.log("saving tags", values);
Static Content }}
</Typography> >
<Typography hierarchy="label" size="s" inverted> {({ editing, Field, formStore }) => (
This is a non-form section with static content <Field name="tags" type="string[]">
</Typography> {(field, props) => (
</SidebarSection> <MachineTags
</> {...splitProps(field, ["value"])[1]}
), size="s"
}, onChange={(newVal) => {
// Workaround for now, until we manage to use native events
setValue(formStore, field.name, newVal);
}}
inverted
required
readOnly={!editing}
orientation="horizontal"
defaultValue={field.value}
/>
)}
</Field>
)}
</SidebarSectionForm>
<SidebarSection title="Simple">
<Typography tag="h2" hierarchy="title" size="m" inverted>
Static Content
</Typography>
<Typography hierarchy="label" size="s" inverted>
This is a non-form section with static content
</Typography>
</SidebarSection>
</>
}
/>
),
}; };

View File

@@ -1,6 +1,5 @@
import { Meta, StoryObj } from "@kachurun/storybook-solid"; import { Meta, StoryObj } from "@kachurun/storybook-solid";
import { Toolbar, ToolbarProps } from "@/src/components/Toolbar/Toolbar"; import { Toolbar, ToolbarProps } from "@/src/components/Toolbar/Toolbar";
import { Divider } from "@/src/components/Divider/Divider";
import { ToolbarButton } from "./ToolbarButton"; import { ToolbarButton } from "./ToolbarButton";
const meta: Meta<ToolbarProps> = { const meta: Meta<ToolbarProps> = {
@@ -13,61 +12,35 @@ export default meta;
type Story = StoryObj<ToolbarProps>; type Story = StoryObj<ToolbarProps>;
export const Default: Story = { export const Default: Story = {
args: { // We have to specify children inside a render function to avoid issues with reactivity outside a solid-js context.
children: (
<>
<ToolbarButton
name="select"
icon="Cursor"
description="Select my thing"
/>
<ToolbarButton
name="new-machine"
icon="NewMachine"
description="Select this thing"
/>
<Divider orientation="vertical" />
<ToolbarButton
name="modules"
icon="Modules"
selected={true}
description="Add service"
/>
<ToolbarButton name="ai" icon="AI" description="Call your AI Manager" />
</>
),
},
};
export const WithTooltip: Story = {
// @ts-expect-error: args in storybook is not typed correctly. This is a storybook issue. // @ts-expect-error: args in storybook is not typed correctly. This is a storybook issue.
render: (args) => ( render: (args) => (
<div class="flex h-[80vh]"> <div class="flex h-[80vh]">
<div class="mt-auto"> <div class="mt-auto">
<Toolbar {...args} /> <Toolbar
{...args}
children={
<>
<ToolbarButton name="select" icon="Cursor" description="Select" />
<ToolbarButton
name="new-machine"
icon="NewMachine"
description="Select"
/>
<ToolbarButton
name="modules"
icon="Modules"
selected={true}
description="Select"
/>
<ToolbarButton name="ai" icon="AI" description="Select" />
</>
}
/>
</div> </div>
</div> </div>
), ),
args: {
children: (
<>
<ToolbarButton name="select" icon="Cursor" description="Select" />
<ToolbarButton
name="new-machine"
icon="NewMachine"
description="Select"
/>
<ToolbarButton
name="modules"
icon="Modules"
selected={true}
description="Select"
/>
<ToolbarButton name="ai" icon="AI" description="Select" />
</>
),
},
}; };

View File

@@ -1,7 +1,6 @@
import { Meta, StoryObj } from "@kachurun/storybook-solid"; import { Meta, StoryObj } from "@kachurun/storybook-solid";
import { Tooltip, TooltipProps } from "@/src/components/Tooltip/Tooltip"; import { Tooltip, TooltipProps } from "@/src/components/Tooltip/Tooltip";
import { Typography } from "@/src/components/Typography/Typography"; import { Typography } from "@/src/components/Typography/Typography";
import { Button } from "@/src/components/Button/Button";
const meta: Meta<TooltipProps> = { const meta: Meta<TooltipProps> = {
title: "Components/Tooltip", title: "Components/Tooltip",
@@ -13,6 +12,23 @@ const meta: Meta<TooltipProps> = {
</div> </div>
), ),
], ],
render: (args: TooltipProps) => (
<div class="p-16">
<Tooltip
{...args}
children={
<Typography
hierarchy="body"
size="xs"
inverted={true}
weight="medium"
>
Your Clan is being created
</Typography>
}
/>
</div>
),
}; };
export default meta; export default meta;
@@ -23,12 +39,6 @@ export const Default: Story = {
args: { args: {
placement: "top", placement: "top",
inverted: false, inverted: false,
trigger: <Button hierarchy="primary">Trigger</Button>,
children: (
<Typography hierarchy="body" size="xs" inverted={true} weight="medium">
Your Clan is being created
</Typography>
),
}, },
}; };

View File

@@ -1,10 +1,5 @@
import { getStepStore, useStepper } from "@/src/hooks/stepper"; import { getStepStore, useStepper } from "@/src/hooks/stepper";
import { import { createForm, SubmitHandler, valiForm } from "@modular-forms/solid";
createForm,
getError,
SubmitHandler,
valiForm,
} from "@modular-forms/solid";
import * as v from "valibot"; import * as v from "valibot";
import { InstallSteps, InstallStoreType } from "../InstallMachine"; import { InstallSteps, InstallStoreType } from "../InstallMachine";
import { Fieldset } from "@/src/components/Form/Fieldset"; import { Fieldset } from "@/src/components/Form/Fieldset";

View File

@@ -5,6 +5,8 @@ import solidSvg from "vite-plugin-solid-svg";
import { patchCssModules } from "vite-css-modules"; import { patchCssModules } from "vite-css-modules";
import path from "node:path"; import path from "node:path";
import { exec } from "child_process"; import { exec } from "child_process";
// @ts-expect-error the type is a bit funky, but it's working
import { storybookTest } from "@storybook/addon-vitest/vitest-plugin";
// watch also clan-cli to catch api changes // watch also clan-cli to catch api changes
const clanCliDir = path.resolve(__dirname, "../../clan-cli"); const clanCliDir = path.resolve(__dirname, "../../clan-cli");
@@ -12,7 +14,7 @@ const clanCliDir = path.resolve(__dirname, "../../clan-cli");
function regenPythonApiOnFileChange() { function regenPythonApiOnFileChange() {
return { return {
name: "run-python-script-on-change", name: "run-python-script-on-change",
handleHotUpdate({}) { handleHotUpdate() {
exec("reload-python-api.sh", (err, stdout, stderr) => { exec("reload-python-api.sh", (err, stdout, stderr) => {
if (err) { if (err) {
console.error(`reload-python-api.sh error:\n${stderr}`); console.error(`reload-python-api.sh error:\n${stderr}`);
@@ -67,4 +69,49 @@ export default defineConfig({
}, },
// assetsInlineLimit: 0, // assetsInlineLimit: 0,
}, },
test: {
projects: [
{
test: {
name: "unit",
},
},
{
extends: path.resolve(__dirname, "vite.config.ts"),
plugins: [
// The plugin will run tests for the stories defined in your Storybook config
// See options at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon#storybooktest
storybookTest({
configDir: path.resolve(__dirname, ".storybook"),
}),
],
test: {
name: "storybook",
browser: {
// Enable browser-based testing for UI components
enabled: true,
headless: true,
provider: "playwright",
instances: [
{
// Ideally we should use webkit to match clan-app, but inside a
// sandboxed nix build, webkit takes forever to finish
// launching. Chromium randomly closes itself during testing, as
// reported here:
// https://github.com/vitest-dev/vitest/discussions/7981
//
// Firefox is the only browser that can reliably finish the
// tests. We want to test storybook only, and the differences
// between browsers are probably neglegible to us
browser: "firefox",
},
],
},
// This setup file applies Storybook project annotations for Vitest
// More info at: https://storybook.js.org/docs/api/portable-stories/portable-stories-vitest#setprojectannotations
setupFiles: [".storybook/vitest.setup.ts"],
},
},
],
},
}); });

View File

@@ -1,60 +0,0 @@
import path from "node:path";
import { fileURLToPath } from "node:url";
import solid from "vite-plugin-solid";
import { defineConfig, mergeConfig } from "vitest/config";
// @ts-expect-error the type is a bit funky, but it's working
import { storybookTest } from "@storybook/addon-vitest/vitest-plugin";
const dirname =
typeof __dirname !== "undefined"
? __dirname
: path.dirname(fileURLToPath(import.meta.url));
import viteConfig from "./vite.config";
export default mergeConfig(
viteConfig,
defineConfig({
plugins: [solid()],
test: {
projects: [
{
test: {
name: "unit",
},
},
{
extends: path.join(dirname, "vite.config.ts"),
plugins: [
// The plugin will run tests for the stories defined in your Storybook config
// See options at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon#storybooktest
storybookTest({
configDir: path.join(dirname, ".storybook"),
}),
],
test: {
name: "storybook",
browser: {
// Enable browser-based testing for UI components
enabled: true,
headless: true,
provider: "playwright",
instances: [
{
browser: "webkit",
launch: {
executablePath: process.env.PLAYWRIGHT_WEBKIT_EXECUTABLE,
},
},
],
},
// This setup file applies Storybook project annotations for Vitest
// More info at: https://storybook.js.org/docs/api/portable-stories/portable-stories-vitest#setprojectannotations
setupFiles: [".storybook/vitest.setup.ts"],
},
},
],
},
}),
);