mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-06 17:38:35 +00:00
style(webui): refine skills mobile experience
This commit is contained in:
parent
655cd80651
commit
9b5a5c5302
@ -146,7 +146,12 @@ export function SkillsCatalogSettings({ skills }: { skills: SkillSummary[] }) {
|
|||||||
className="h-9 rounded-[11px] bg-background pl-9 text-[13px]"
|
className="h-9 rounded-[11px] bg-background pl-9 text-[13px]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1 rounded-[10px] bg-muted/65 p-1">
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex max-w-full items-center gap-1 overflow-x-auto rounded-[10px] bg-muted/65 p-1",
|
||||||
|
"scrollbar-thin scrollbar-track-transparent sm:w-auto",
|
||||||
|
)}
|
||||||
|
>
|
||||||
{([
|
{([
|
||||||
["all", t("settings.skills.filterAll", { defaultValue: "All" }), skills.length],
|
["all", t("settings.skills.filterAll", { defaultValue: "All" }), skills.length],
|
||||||
[
|
[
|
||||||
@ -165,7 +170,7 @@ export function SkillsCatalogSettings({ skills }: { skills: SkillSummary[] }) {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => setInstalledFilter(filter)}
|
onClick={() => setInstalledFilter(filter)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"rounded-[8px] px-2.5 py-1 text-[11px] font-medium transition-colors",
|
"shrink-0 whitespace-nowrap rounded-[8px] px-2.5 py-1 text-[11px] font-medium transition-colors",
|
||||||
installedFilter === filter
|
installedFilter === filter
|
||||||
? "bg-background text-foreground shadow-sm"
|
? "bg-background text-foreground shadow-sm"
|
||||||
: "text-muted-foreground hover:text-foreground",
|
: "text-muted-foreground hover:text-foreground",
|
||||||
@ -314,6 +319,7 @@ function SkillDetailSheet({
|
|||||||
const [actionError, setActionError] = useState("");
|
const [actionError, setActionError] = useState("");
|
||||||
const [deleteOpen, setDeleteOpen] = useState(false);
|
const [deleteOpen, setDeleteOpen] = useState(false);
|
||||||
const [refreshKey, setRefreshKey] = useState(0);
|
const [refreshKey, setRefreshKey] = useState(0);
|
||||||
|
const [descriptionExpanded, setDescriptionExpanded] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!open || !skill) return;
|
if (!open || !skill) return;
|
||||||
@ -323,6 +329,7 @@ function SkillDetailSheet({
|
|||||||
setLoadFailed(false);
|
setLoadFailed(false);
|
||||||
setActionError("");
|
setActionError("");
|
||||||
setDeleteOpen(false);
|
setDeleteOpen(false);
|
||||||
|
setDescriptionExpanded(false);
|
||||||
fetchSkillDetail(token, skill.name)
|
fetchSkillDetail(token, skill.name)
|
||||||
.then((payload) => {
|
.then((payload) => {
|
||||||
if (!cancelled) setDetail(payload);
|
if (!cancelled) setDetail(payload);
|
||||||
@ -349,6 +356,7 @@ function SkillDetailSheet({
|
|||||||
: activeSkill.available
|
: activeSkill.available
|
||||||
? t("settings.skills.statusEnabled", { defaultValue: "Enabled" })
|
? t("settings.skills.statusEnabled", { defaultValue: "Enabled" })
|
||||||
: t("settings.skills.statusNeedsSetup", { defaultValue: "Needs setup" });
|
: t("settings.skills.statusNeedsSetup", { defaultValue: "Needs setup" });
|
||||||
|
const descriptionExpandable = activeSkill.description.length > 240;
|
||||||
|
|
||||||
const toggleEnabled = async () => {
|
const toggleEnabled = async () => {
|
||||||
setActionBusy(true);
|
setActionBusy(true);
|
||||||
@ -399,12 +407,25 @@ function SkillDetailSheet({
|
|||||||
<Sheet open={open} onOpenChange={onOpenChange}>
|
<Sheet open={open} onOpenChange={onOpenChange}>
|
||||||
<SheetContent
|
<SheetContent
|
||||||
side="right"
|
side="right"
|
||||||
className="w-[min(34rem,calc(100vw-1rem))] max-w-none gap-0 overflow-hidden p-0 sm:max-w-none"
|
closeButtonClassName={cn(
|
||||||
|
"right-2 top-2 inline-flex h-10 w-10 items-center justify-center rounded-full opacity-100",
|
||||||
|
"text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
|
||||||
|
"sm:right-3 sm:top-3",
|
||||||
|
)}
|
||||||
|
className={cn(
|
||||||
|
"w-full max-w-none gap-0 overflow-hidden border-l-0 p-0",
|
||||||
|
"sm:w-[min(34rem,calc(100vw-1rem))] sm:max-w-none sm:border-l",
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<div className="min-h-0 flex-1 overflow-y-auto px-5 py-5">
|
<div
|
||||||
|
className={cn(
|
||||||
|
"min-h-0 flex-1 overflow-y-auto px-4 py-4 sm:px-5 sm:py-5",
|
||||||
|
"pb-[max(1rem,env(safe-area-inset-bottom))]",
|
||||||
|
)}
|
||||||
|
>
|
||||||
<div className="flex items-start gap-3 pr-8">
|
<div className="flex items-start gap-3 pr-8">
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<SheetTitle className="truncate text-[20px] font-semibold">
|
<SheetTitle className="truncate text-[19px] font-semibold sm:text-[20px]">
|
||||||
{activeSkill.name}
|
{activeSkill.name}
|
||||||
</SheetTitle>
|
</SheetTitle>
|
||||||
<SheetDescription className="sr-only">
|
<SheetDescription className="sr-only">
|
||||||
@ -427,9 +448,26 @@ function SkillDetailSheet({
|
|||||||
{statusLabel}
|
{statusLabel}
|
||||||
</Pill>
|
</Pill>
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-3 text-[13px] leading-5 text-muted-foreground">
|
<p
|
||||||
|
className={cn(
|
||||||
|
"mt-3 text-[13px] leading-5 text-muted-foreground",
|
||||||
|
descriptionExpandable && !descriptionExpanded && "line-clamp-5",
|
||||||
|
)}
|
||||||
|
>
|
||||||
{activeSkill.description}
|
{activeSkill.description}
|
||||||
</p>
|
</p>
|
||||||
|
{descriptionExpandable ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
aria-expanded={descriptionExpanded}
|
||||||
|
onClick={() => setDescriptionExpanded((value) => !value)}
|
||||||
|
className="mt-1.5 min-h-8 rounded-full text-[12px] font-medium text-foreground/70 transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||||
|
>
|
||||||
|
{descriptionExpanded
|
||||||
|
? t("settings.skills.showLess", { defaultValue: "Show less" })
|
||||||
|
: t("settings.skills.showMore", { defaultValue: "Show more" })}
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -444,7 +482,7 @@ function SkillDetailSheet({
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="mt-6 space-y-5">
|
<div className="mt-6 space-y-5">
|
||||||
<div className="flex items-center justify-between gap-4 border-y border-border/45 px-1 py-3.5">
|
<div className="flex min-h-16 items-start justify-between gap-3 border-y border-border/45 px-1 py-3.5">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-[13px] font-medium text-foreground">
|
<p className="text-[13px] font-medium text-foreground">
|
||||||
{t("settings.skills.enabledControl", { defaultValue: "Use this skill" })}
|
{t("settings.skills.enabledControl", { defaultValue: "Use this skill" })}
|
||||||
@ -467,15 +505,20 @@ function SkillDetailSheet({
|
|||||||
disabled={actionBusy}
|
disabled={actionBusy}
|
||||||
onClick={() => void toggleEnabled()}
|
onClick={() => void toggleEnabled()}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative h-6 w-11 shrink-0 rounded-full transition-colors",
|
"relative -mr-1 h-10 w-14 shrink-0 rounded-full",
|
||||||
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
||||||
"disabled:cursor-wait disabled:opacity-60",
|
"disabled:cursor-wait disabled:opacity-60",
|
||||||
enabled ? "bg-foreground" : "bg-muted-foreground/30",
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
"absolute left-0.5 top-0.5 h-5 w-5 rounded-full bg-background shadow-sm transition-transform",
|
"absolute left-1.5 top-2 h-6 w-11 rounded-full transition-colors",
|
||||||
|
enabled ? "bg-foreground" : "bg-muted-foreground/30",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"absolute left-2 top-2.5 h-5 w-5 rounded-full bg-background shadow-sm transition-transform",
|
||||||
enabled ? "translate-x-5" : "translate-x-0",
|
enabled ? "translate-x-5" : "translate-x-0",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -498,7 +541,7 @@ function SkillDetailSheet({
|
|||||||
{detail ? <RawInstructionsBlock markdown={detail.raw_markdown} /> : null}
|
{detail ? <RawInstructionsBlock markdown={detail.raw_markdown} /> : null}
|
||||||
|
|
||||||
{deletable ? (
|
{deletable ? (
|
||||||
<div className="flex items-center justify-between gap-4 border-t border-border/45 pt-5">
|
<div className="flex flex-col items-stretch gap-3 border-t border-border/45 pt-5 sm:flex-row sm:items-center sm:justify-between sm:gap-4">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-[13px] font-medium text-foreground">
|
<p className="text-[13px] font-medium text-foreground">
|
||||||
{t("settings.skills.deleteTitle", { defaultValue: "Delete skill" })}
|
{t("settings.skills.deleteTitle", { defaultValue: "Delete skill" })}
|
||||||
@ -511,11 +554,14 @@ function SkillDetailSheet({
|
|||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="destructive"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
disabled={actionBusy}
|
disabled={actionBusy}
|
||||||
onClick={() => setDeleteOpen(true)}
|
onClick={() => setDeleteOpen(true)}
|
||||||
className="shrink-0 rounded-full"
|
className={cn(
|
||||||
|
"h-10 w-full shrink-0 rounded-full border-destructive/20 text-destructive",
|
||||||
|
"hover:bg-destructive/8 hover:text-destructive sm:h-9 sm:w-auto",
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<Trash2 className="mr-1.5 h-3.5 w-3.5" aria-hidden />
|
<Trash2 className="mr-1.5 h-3.5 w-3.5" aria-hidden />
|
||||||
{t("settings.skills.deleteAction", { defaultValue: "Delete" })}
|
{t("settings.skills.deleteAction", { defaultValue: "Delete" })}
|
||||||
@ -571,7 +617,7 @@ function RawInstructionsBlock({ markdown }: { markdown: string }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<details className="group rounded-[18px] border border-border/45 bg-muted/20 px-3 py-3">
|
<details className="group rounded-[18px] border border-border/45 bg-muted/20 px-3 py-3">
|
||||||
<summary className="flex cursor-pointer select-none items-center justify-between gap-3 text-[13px] font-medium text-foreground/90 transition-colors hover:text-foreground">
|
<summary className="flex min-h-11 cursor-pointer select-none items-center justify-between gap-3 text-[13px] font-medium text-foreground/90 transition-colors hover:text-foreground">
|
||||||
<span>
|
<span>
|
||||||
{t("settings.skills.instructionsTitle", { defaultValue: "Skill instructions" })}
|
{t("settings.skills.instructionsTitle", { defaultValue: "Skill instructions" })}
|
||||||
</span>
|
</span>
|
||||||
@ -657,7 +703,7 @@ function RequirementsSection({
|
|||||||
})}
|
})}
|
||||||
title={option.label}
|
title={option.label}
|
||||||
onClick={() => void copyCommand(option.command)}
|
onClick={() => void copyCommand(option.command)}
|
||||||
className="flex h-7 w-7 shrink-0 items-center justify-center rounded-[8px] text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
|
className="flex h-9 w-9 shrink-0 items-center justify-center rounded-[9px] text-muted-foreground transition-colors hover:bg-muted hover:text-foreground sm:h-7 sm:w-7"
|
||||||
>
|
>
|
||||||
{copiedCommand === option.command ? (
|
{copiedCommand === option.command ? (
|
||||||
<Check className="h-3.5 w-3.5 text-emerald-600" aria-hidden />
|
<Check className="h-3.5 w-3.5 text-emerald-600" aria-hidden />
|
||||||
@ -689,7 +735,7 @@ function RequirementsSection({
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={onRefresh}
|
onClick={onRefresh}
|
||||||
className="mt-3 h-8 rounded-full bg-background/60 px-3 text-[11px]"
|
className="mt-3 h-9 rounded-full bg-background/60 px-3 text-[11px]"
|
||||||
>
|
>
|
||||||
<RefreshCw className="mr-1.5 h-3.5 w-3.5" aria-hidden />
|
<RefreshCw className="mr-1.5 h-3.5 w-3.5" aria-hidden />
|
||||||
{t("settings.skills.checkAgain", { defaultValue: "Check again" })}
|
{t("settings.skills.checkAgain", { defaultValue: "Check again" })}
|
||||||
|
|||||||
@ -198,7 +198,7 @@ export function SkillsMarketplace({ installedSkills }: { installedSkills: SkillS
|
|||||||
|
|
||||||
{query.trim().length < 2 ? (
|
{query.trim().length < 2 ? (
|
||||||
<section className="overflow-hidden rounded-[22px] bg-settings-surface">
|
<section className="overflow-hidden rounded-[22px] bg-settings-surface">
|
||||||
<div className="flex items-center justify-between border-b border-border/45 px-5 py-4">
|
<div className="flex flex-col items-start gap-2 border-b border-border/45 px-4 py-4 sm:flex-row sm:items-center sm:justify-between sm:px-5">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-[14px] font-semibold">
|
<h2 className="text-[14px] font-semibold">
|
||||||
{t("settings.skills.marketplaceTrendingTitle", {
|
{t("settings.skills.marketplaceTrendingTitle", {
|
||||||
@ -371,9 +371,9 @@ function MarketplaceSkillRow({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex min-w-0 items-center gap-3 px-2 py-3.5">
|
<div className="flex min-w-0 items-center gap-2 px-1 py-3.5 sm:gap-3 sm:px-2">
|
||||||
{skill.rank ? (
|
{skill.rank ? (
|
||||||
<span className="w-7 shrink-0 text-right font-mono text-[12px] tabular-nums text-muted-foreground/65">
|
<span className="w-6 shrink-0 text-right font-mono text-[11px] tabular-nums text-muted-foreground/65 sm:w-7 sm:text-[12px]">
|
||||||
#{skill.rank}
|
#{skill.rank}
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
@ -390,7 +390,7 @@ function MarketplaceSkillRow({
|
|||||||
name: skill.name,
|
name: skill.name,
|
||||||
defaultValue: "Open {{name}} on skills.sh",
|
defaultValue: "Open {{name}} on skills.sh",
|
||||||
})}
|
})}
|
||||||
className="shrink-0 text-muted-foreground transition-colors hover:text-foreground"
|
className="inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
|
||||||
>
|
>
|
||||||
<ExternalLink className="h-3.5 w-3.5" aria-hidden />
|
<ExternalLink className="h-3.5 w-3.5" aria-hidden />
|
||||||
</a>
|
</a>
|
||||||
@ -418,7 +418,10 @@ function MarketplaceSkillRow({
|
|||||||
variant={installed ? "secondary" : "default"}
|
variant={installed ? "secondary" : "default"}
|
||||||
disabled={installed || installBusy || installSupported === false}
|
disabled={installed || installBusy || installSupported === false}
|
||||||
onClick={() => onSelect(skill)}
|
onClick={() => onSelect(skill)}
|
||||||
className={cn("min-w-[92px] rounded-full", installed && "text-emerald-700")}
|
className={cn(
|
||||||
|
"min-w-[82px] rounded-full px-2.5 sm:min-w-[92px] sm:px-3",
|
||||||
|
installed && "text-emerald-700",
|
||||||
|
)}
|
||||||
title={
|
title={
|
||||||
installSupported === false
|
installSupported === false
|
||||||
? t("settings.skills.marketplaceNpxRequired", {
|
? t("settings.skills.marketplaceNpxRequired", {
|
||||||
@ -512,7 +515,7 @@ function TrendingSkeleton() {
|
|||||||
<div className="h-3.5 w-48 max-w-[55%] animate-pulse rounded bg-muted" />
|
<div className="h-3.5 w-48 max-w-[55%] animate-pulse rounded bg-muted" />
|
||||||
<div className="h-3 w-32 max-w-[40%] animate-pulse rounded bg-muted/70" />
|
<div className="h-3 w-32 max-w-[40%] animate-pulse rounded bg-muted/70" />
|
||||||
</div>
|
</div>
|
||||||
<div className="h-8 w-[92px] animate-pulse rounded-full bg-muted" />
|
<div className="h-8 w-[82px] animate-pulse rounded-full bg-muted sm:w-[92px]" />
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -57,13 +57,24 @@ const sheetVariants = cva(
|
|||||||
interface SheetContentProps
|
interface SheetContentProps
|
||||||
extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>,
|
extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>,
|
||||||
VariantProps<typeof sheetVariants> {
|
VariantProps<typeof sheetVariants> {
|
||||||
|
closeButtonClassName?: string;
|
||||||
showCloseButton?: boolean;
|
showCloseButton?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SheetContent = React.forwardRef<
|
const SheetContent = React.forwardRef<
|
||||||
React.ElementRef<typeof DialogPrimitive.Content>,
|
React.ElementRef<typeof DialogPrimitive.Content>,
|
||||||
SheetContentProps
|
SheetContentProps
|
||||||
>(({ side = "right", className, children, showCloseButton = true, ...props }, ref) => (
|
>((
|
||||||
|
{
|
||||||
|
side = "right",
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
closeButtonClassName,
|
||||||
|
showCloseButton = true,
|
||||||
|
...props
|
||||||
|
},
|
||||||
|
ref,
|
||||||
|
) => (
|
||||||
<SheetPortal>
|
<SheetPortal>
|
||||||
<SheetOverlay />
|
<SheetOverlay />
|
||||||
<DialogPrimitive.Content
|
<DialogPrimitive.Content
|
||||||
@ -78,7 +89,14 @@ const SheetContent = React.forwardRef<
|
|||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
{showCloseButton ? (
|
{showCloseButton ? (
|
||||||
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none">
|
<DialogPrimitive.Close
|
||||||
|
className={cn(
|
||||||
|
"absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity",
|
||||||
|
"hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
||||||
|
"disabled:pointer-events-none",
|
||||||
|
closeButtonClassName,
|
||||||
|
)}
|
||||||
|
>
|
||||||
<X className="h-4 w-4" />
|
<X className="h-4 w-4" />
|
||||||
<span className="sr-only">Close</span>
|
<span className="sr-only">Close</span>
|
||||||
</DialogPrimitive.Close>
|
</DialogPrimitive.Close>
|
||||||
|
|||||||
@ -378,6 +378,12 @@ describe("App layout", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("opens Skills from the main sidebar", async () => {
|
it("opens Skills from the main sidebar", async () => {
|
||||||
|
const longSkillDescription = [
|
||||||
|
"Work with GitHub repositories, issues, pull requests, releases, workflows,",
|
||||||
|
"and code search through the GitHub CLI.",
|
||||||
|
"Use this skill for repository maintenance, review automation, release preparation,",
|
||||||
|
"and other GitHub workflows that need authenticated command-line access.",
|
||||||
|
].join(" ");
|
||||||
mockFetchRoutes({
|
mockFetchRoutes({
|
||||||
"/api/settings": baseSettingsPayload(),
|
"/api/settings": baseSettingsPayload(),
|
||||||
"/api/settings/cli-apps": { apps: [], installed_count: 0, catalog_updated_at: "2026-04-18" },
|
"/api/settings/cli-apps": { apps: [], installed_count: 0, catalog_updated_at: "2026-04-18" },
|
||||||
@ -413,7 +419,7 @@ describe("App layout", () => {
|
|||||||
},
|
},
|
||||||
"/api/webui/skills/github": {
|
"/api/webui/skills/github": {
|
||||||
name: "github",
|
name: "github",
|
||||||
description: "Work with GitHub.",
|
description: longSkillDescription,
|
||||||
source: "builtin",
|
source: "builtin",
|
||||||
enabled: true,
|
enabled: true,
|
||||||
deletable: false,
|
deletable: false,
|
||||||
@ -501,6 +507,13 @@ describe("App layout", () => {
|
|||||||
fireEvent.click(screen.getByRole("button", { name: "Open details for github" }));
|
fireEvent.click(screen.getByRole("button", { name: "Open details for github" }));
|
||||||
|
|
||||||
expect(await screen.findByRole("heading", { name: "github" })).toBeInTheDocument();
|
expect(await screen.findByRole("heading", { name: "github" })).toBeInTheDocument();
|
||||||
|
const showMore = await screen.findByRole("button", { name: "Show more" });
|
||||||
|
expect(showMore).toHaveAttribute("aria-expanded", "false");
|
||||||
|
fireEvent.click(showMore);
|
||||||
|
expect(screen.getByRole("button", { name: "Show less" })).toHaveAttribute(
|
||||||
|
"aria-expanded",
|
||||||
|
"true",
|
||||||
|
);
|
||||||
expect(screen.getByText("Setup required")).toBeInTheDocument();
|
expect(screen.getByText("Setup required")).toBeInTheDocument();
|
||||||
expect(screen.getByText("brew install gh")).toBeInTheDocument();
|
expect(screen.getByText("brew install gh")).toBeInTheDocument();
|
||||||
expect(screen.queryByText("Unavailable reason")).not.toBeInTheDocument();
|
expect(screen.queryByText("Unavailable reason")).not.toBeInTheDocument();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user