Merge pull request 'various-ui-fixes' (#5448) from various-ui-fixes into main

Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5448
This commit is contained in:
brianmcgee
2025-10-09 14:22:06 +00:00
12 changed files with 114 additions and 145 deletions

View File

@@ -113,15 +113,27 @@ mkShell {
# todo darwin support needs some work # todo darwin support needs some work
(lib.optionalString stdenv.hostPlatform.isLinux '' (lib.optionalString stdenv.hostPlatform.isLinux ''
# configure playwright for storybook snapshot testing # configure playwright for storybook snapshot testing
export PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 # we only want webkit as that matches what the app is rendered with
export PLAYWRIGHT_BROWSERS_PATH=${ export PLAYWRIGHT_BROWSERS_PATH=${
playwright-driver.browsers.override { playwright-driver.browsers.override {
withFfmpeg = false; withFfmpeg = false;
withFirefox = false; withFirefox = false;
withWebkit = true;
withChromium = false; withChromium = false;
withChromiumHeadlessShell = true; withChromiumHeadlessShell = false;
} }
} }
export PLAYWRIGHT_HOST_PLATFORM_OVERRIDE="ubuntu-24.04"
# stop playwright from trying to validate it has downloaded the necessary browsers
# we are providing them manually via nix
export PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS=true
# 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

@@ -53,7 +53,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.53.2", "playwright": "~1.55.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",
@@ -6956,13 +6956,13 @@
} }
}, },
"node_modules/playwright": { "node_modules/playwright": {
"version": "1.53.2", "version": "1.55.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.2.tgz", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.55.1.tgz",
"integrity": "sha512-6K/qQxVFuVQhRQhFsVZ9fGeatxirtrpPgxzBYWyZLEXJzqYwuL4fuNmfOfD5et1tJE4GScKyPNeLhZeRwuTU3A==", "integrity": "sha512-cJW4Xd/G3v5ovXtJJ52MAOclqeac9S/aGGgRzLabuF8TnIb6xHvMzKIa6JmrRzUkeXJgfL1MhukP0NK6l39h3A==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"playwright-core": "1.53.2" "playwright-core": "1.55.1"
}, },
"bin": { "bin": {
"playwright": "cli.js" "playwright": "cli.js"
@@ -6975,9 +6975,9 @@
} }
}, },
"node_modules/playwright-core": { "node_modules/playwright-core": {
"version": "1.53.2", "version": "1.55.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.53.2.tgz", "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.1.tgz",
"integrity": "sha512-ox/OytMy+2w1jcYEYlOo1Hhp8hZkLCximMTUTMBXjGUA1KoFfiSZ+DU+3a739jsPY0yoKH2TFy9S2fsJas8yAw==", "integrity": "sha512-Z6Mh9mkwX+zxSlHqdr5AOcJnfp+xUWLCt9uKV18fhzA8eyxUd8NUWzAjxUh55RZKSYwDGX0cfaySdhZJGMoJ+w==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"bin": { "bin": {

View File

@@ -48,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.53.2", "playwright": "~1.55.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 } from "storybook/test"; import { expect, fn, waitFor, 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;
@@ -216,17 +216,11 @@ const timeout = process.env.NODE_ENV === "test" ? 500 : 2000;
export const Primary: Story = { export const Primary: Story = {
args: { args: {
hierarchy: "primary", hierarchy: "primary",
onAction: fn(async () => { onClick: fn(),
// wait 500 ms to simulate an action
await new Promise((resolve) => setTimeout(resolve, timeout));
// randomly fail to check that the loading state still returns to normal
if (Math.random() > 0.5) {
throw new Error("Action failure");
}
}),
}, },
play: async ({ canvas, step, userEvent, args }: StoryContext) => { play: async ({ canvasElement, step, userEvent, args }: StoryContext) => {
const canvas = within(canvasElement);
const buttons = await canvas.findAllByRole("button"); const buttons = await canvas.findAllByRole("button");
for (const button of buttons) { for (const button of buttons) {
@@ -238,14 +232,6 @@ export const Primary: Story = {
} }
await step(`Click on ${testID}`, async () => { await step(`Click on ${testID}`, async () => {
// check for the loader
const loaders = button.getElementsByClassName("loader");
await expect(loaders.length).toEqual(1);
// assert its width is 0 before we click
const [loader] = loaders;
await expect(loader.clientWidth).toEqual(0);
// move the mouse over the button // move the mouse over the button
await userEvent.hover(button); await userEvent.hover(button);
@@ -255,33 +241,8 @@ export const Primary: Story = {
// click the button // click the button
await userEvent.click(button); await userEvent.click(button);
// check the button has changed // the click handler should have been called
await waitFor( await expect(args.onClick).toHaveBeenCalled();
async () => {
// the action handler should have been called
await expect(args.onAction).toHaveBeenCalled();
// the button should have a loading class
await expect(button).toHaveClass("loading");
// the loader should be visible
await expect(loader.clientWidth).toBeGreaterThan(0);
// the pointer should have changed to wait
await expect(getCursorStyle(button)).toEqual("wait");
},
{ timeout: timeout + 500 },
);
// wait for the action handler to finish
await waitFor(
async () => {
// the loading class should be removed
await expect(button).not.toHaveClass("loading");
// the loader should be hidden
await expect(loader.clientWidth).toEqual(0);
// the pointer should be normal
await expect(getCursorStyle(button)).toEqual("pointer");
},
{ timeout: timeout + 500 },
);
}); });
} }
}, },

View File

@@ -57,6 +57,7 @@ export const Button = (props: ButtonProps) => {
return ( return (
<KobalteButton <KobalteButton
role="button"
class={cx( class={cx(
styles.button, // default button class styles.button, // default button class
local.size != "default" && styles[local.size], local.size != "default" && styles[local.size],

View File

@@ -160,47 +160,47 @@ const mockFetcher = <K extends OperationNames>(
}, },
}) satisfies ApiCall<K>; }) satisfies ApiCall<K>;
export const Default: Story = { // export const Default: Story = {
args: {}, // args: {},
decorators: [ // decorators: [
(Story: StoryObj) => { // (Story: StoryObj) => {
const queryClient = new QueryClient({ // const queryClient = new QueryClient({
defaultOptions: { // defaultOptions: {
queries: { // queries: {
retry: false, // retry: false,
staleTime: Infinity, // staleTime: Infinity,
}, // },
}, // },
}); // });
//
Object.entries(queryData).forEach(([clanURI, clan]) => { // Object.entries(queryData).forEach(([clanURI, clan]) => {
queryClient.setQueryData( // queryClient.setQueryData(
["clans", encodeBase64(clanURI), "details"], // ["clans", encodeBase64(clanURI), "details"],
clan.details, // clan.details,
); // );
//
const machines = clan.machines || {}; // const machines = clan.machines || {};
//
queryClient.setQueryData( // queryClient.setQueryData(
["clans", encodeBase64(clanURI), "machines"], // ["clans", encodeBase64(clanURI), "machines"],
machines, // machines,
); // );
//
Object.entries(machines).forEach(([name, machine]) => { // Object.entries(machines).forEach(([name, machine]) => {
queryClient.setQueryData( // queryClient.setQueryData(
["clans", encodeBase64(clanURI), "machine", name, "state"], // ["clans", encodeBase64(clanURI), "machine", name, "state"],
machine.state, // machine.state,
); // );
}); // });
}); // });
//
return ( // return (
<ApiClientProvider client={{ fetch: mockFetcher }}> // <ApiClientProvider client={{ fetch: mockFetcher }}>
<QueryClientProvider client={queryClient}> // <QueryClientProvider client={queryClient}>
<Story /> // <Story />
</QueryClientProvider> // </QueryClientProvider>
</ApiClientProvider> // </ApiClientProvider>
); // );
}, // },
], // ],
}; // };

View File

@@ -22,9 +22,9 @@ import { Alert } from "@/src/components/Alert/Alert";
import { removeClanURI } from "@/src/stores/clan"; import { removeClanURI } from "@/src/stores/clan";
const schema = v.object({ const schema = v.object({
name: v.pipe(v.optional(v.string())), name: v.string(),
description: v.nullish(v.string()), description: v.optional(v.string()),
icon: v.pipe(v.nullish(v.string())), icon: v.optional(v.string()),
}); });
export interface ClanSettingsModalProps { export interface ClanSettingsModalProps {

View File

@@ -1,15 +0,0 @@
import { Meta, StoryObj } from "@kachurun/storybook-solid";
import { CubeScene } from "./cubes";
const meta: Meta = {
title: "scene/cubes",
component: CubeScene,
};
export default meta;
type Story = StoryObj;
export const Default: Story = {
args: {},
};

View File

@@ -304,11 +304,10 @@ const FlashProgress = () => {
const [store, set] = getStepStore<InstallStoreType>(stepSignal); const [store, set] = getStepStore<InstallStoreType>(stepSignal);
onMount(async () => { onMount(async () => {
const result = await store.flash.progress.result; const result = await store.flash?.progress?.result;
if (result.status == "success") { if (result?.status == "success") {
console.log("Flashing Success"); stepSignal.next();
} }
stepSignal.next();
}); });
const handleCancel = async () => { const handleCancel = async () => {

View File

@@ -165,23 +165,23 @@ export default meta;
type Story = StoryObj<typeof ServiceWorkflow>; type Story = StoryObj<typeof ServiceWorkflow>;
export const Default: Story = { // export const Default: Story = {
args: {}, // args: {},
}; // };
//
export const SelectRoleMembers: Story = { // export const SelectRoleMembers: Story = {
render: () => ( // render: () => (
<ServiceWorkflow // <ServiceWorkflow
handleSubmit={(instance) => { // handleSubmit={(instance) => {
console.log("Submitted instance:", instance); // console.log("Submitted instance:", instance);
}} // }}
onClose={() => { // onClose={() => {
console.log("Closed"); // console.log("Closed");
}} // }}
initialStep="select:members" // initialStep="select:members"
initialStore={{ // initialStore={{
currentRole: "peer", // currentRole: "peer",
}} // }}
/> // />
), // ),
}; // };

View File

@@ -9,7 +9,11 @@
"esModuleInterop": true, "esModuleInterop": true,
"jsx": "preserve", "jsx": "preserve",
"jsxImportSource": "solid-js", "jsxImportSource": "solid-js",
"types": ["vite/client", "vite-plugin-solid-svg/types-component-solid"], "types": [
"vite/client",
"vite-plugin-solid-svg/types-component-solid",
"@vitest/browser/providers/playwright"
],
"noEmit": true, "noEmit": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"allowJs": true, "allowJs": true,

View File

@@ -40,7 +40,14 @@ export default mergeConfig(
enabled: true, enabled: true,
headless: true, headless: true,
provider: "playwright", provider: "playwright",
instances: [{ browser: "chromium" }], instances: [
{
browser: "webkit",
launch: {
executablePath: process.env.PLAYWRIGHT_WEBKIT_EXECUTABLE,
},
},
],
}, },
// This setup file applies Storybook project annotations for Vitest // This setup file applies Storybook project annotations for Vitest
// More info at: https://storybook.js.org/docs/api/portable-stories/portable-stories-vitest#setprojectannotations // More info at: https://storybook.js.org/docs/api/portable-stories/portable-stories-vitest#setprojectannotations