@@ -0,0 +1,108 @@
1+module Test.HeroUI.HeroUI3Spec where
2+
3+import Prelude
4+
5+import Effect.Class (liftEffect)
6+import HeroUI.Button as Btn
7+import HeroUI.Card as Card
8+import HeroUI.Checkbox as Checkbox
9+import HeroUI.Chip as Chip
10+import HeroUI.Hooks as Hooks
11+import HeroUI.Input as Input
12+import HeroUI.Separator as Separator
13+import HeroUI.Spinner as Spinner
14+import HeroUI.Switch as Switch
15+import HeroUI.Toast as Toast
16+import HeroUI.Types as T
17+import React.Basic (JSX)
18+import React.Basic.Hooks as React
19+import React.TestingLibrary (cleanup, render)
20+import Test.Spec (Spec, after_, describe, it)
21+import Test.Spec.Assertions (shouldEqual)
22+import Test.Spec.Assertions.DOM (textContentShouldEqual)
23+import Yoga.React (component)
24+import Yoga.React.DOM.Internal (text)
25+
26+spec :: Spec Unit
27+spec = after_ cleanup $ describe "purescript-yoga-heroui3 (HeroUI v3)" do
28+ it "Button renders children and accepts a typed variant + type" do
29+ { findByText } <- render submitButton
30+ btn <- findByText "Submit"
31+ btn `textContentShouldEqual` "Submit"
32+
33+ it "Aria props compile and reach the DOM (no provider needed in v3)" do
34+ { findByLabelText } <- render ariaButton
35+ el <- findByLabelText "close dialog"
36+ el `textContentShouldEqual` "X"
37+
38+ it "Card renders as a compound component" do
39+ { findByText } <- render cardHost
40+ title <- findByText "Card title"
41+ title `textContentShouldEqual` "Card title"
42+
43+ it "Chip accepts color/size/variant from the shared + per-component types" do
44+ { findByText } <- render chipHost
45+ el <- findByText "new"
46+ el `textContentShouldEqual` "new"
47+
48+ it "Switch and Checkbox accept boolean state props" do
49+ _ <- render switchAndCheckbox
50+ pure unit
51+
52+ it "Spinner and Separator render" do
53+ _ <- render spinnerAndSeparator
54+ pure unit
55+
56+ it "useOverlayState exposes isOpen (replaces v2 useDisclosure)" do
57+ { findByText } <- render overlayHost
58+ initial <- findByText "closed"
59+ initial `textContentShouldEqual` "closed"
60+
61+ it "Toast imperative API (success/close/clear) is callable" do
62+ key <- liftEffect $ Toast.toastSuccess (text "saved")
63+ liftEffect $ Toast.closeToast key
64+ liftEffect Toast.closeAllToasts
65+
66+ it "InputType strings round-trip" do
67+ Input.inputTypeToString Input.InputTypeEmail `shouldEqual` "email"
68+ Input.inputTypeToString Input.InputTypeDateTimeLocal `shouldEqual` "datetime-local"
69+
70+submitButton :: JSX
71+submitButton =
72+ Btn.button { variant: Btn.Primary, type: Btn.ButtonTypeSubmit } (text "Submit")
73+
74+ariaButton :: JSX
75+ariaButton = Btn.button { "aria-label": "close dialog" } (text "X")
76+
77+cardHost :: JSX
78+cardHost =
79+ Card.card { variant: Card.Default }
80+ [ Card.cardHeader {} [ Card.cardTitle {} (text "Card title") ]
81+ , Card.cardContent {} (text "body")
82+ ]
83+
84+chipHost :: JSX
85+chipHost =
86+ Chip.chip { color: T.Accent, size: T.Sm, variant: Chip.Soft } (text "new")
87+
88+switchAndCheckbox :: JSX
89+switchAndCheckbox =
90+ Card.card {}
91+ [ Switch.switch_ { defaultSelected: true, size: T.Md } (text "on")
92+ , Checkbox.checkbox { defaultSelected: false, variant: T.Primary } (text "agree")
93+ ]
94+
95+spinnerAndSeparator :: JSX
96+spinnerAndSeparator =
97+ Card.card {}
98+ [ Spinner.spinner { color: Spinner.Accent, size: Spinner.Md } ([] :: Array JSX)
99+ , Separator.separator { orientation: T.Horizontal } ([] :: Array JSX)
100+ ]
101+
102+overlayHost :: JSX
103+overlayHost = overlayComponent {}
104+
105+overlayComponent :: {} -> JSX
106+overlayComponent = component "OverlayHost" \(_ :: {}) -> React.do
107+ state <- Hooks.useOverlayState {}
108+ pure $ text (if state.isOpen then "open" else "closed")