Use gen/let to generate :type first, then branch the rest of the map on it — this expresses a cross-key invariant (e.g. :info requires [:button :dismiss] to be present and non-nil, :critical allows it to be absent).
(require '[clojure.test.check.generators :as gen])
(def alert-widget-gen
(gen/let [type (gen/elements [:info :critical])
button (if (= type :info)
(gen/fmap (fn [s] {:dismiss s}) gen/string-alphanumeric)
(gen/one-of [(gen/return {})
(gen/fmap (fn [s] {:dismiss s}) gen/string-alphanumeric)]))]
{:type type :button button}))
(gen/sample alert-widget-gen 5)gen/let is sugar over gen/bind/gen/fmap and lets later bindings depend on earlier ones (monadic bind), which is what makes the conditional branching possible.
API docs: https://clojure.github.io/test.check/clojure.test.check.generators.html
Yes — built in via s/pred and s/constrained.
(require '[schema.core :as s])
;; s/pred — wrap a bare predicate as a schema
(def NonEmptyString (s/pred #(and (string? %) (seq %)) 'non-empty-string))
(s/validate NonEmptyString "X") ;; passes
(s/validate NonEmptyString "") ;; throws
;; s/constrained — attach a predicate as an additional invariant on an existing schema
(def PositiveInt (s/constrained s/Int pos? 'pos?))
(s/validate PositiveInt 5) ;; passes
(s/validate PositiveInt -5) ;; throwsFor a cross-key invariant like the alert-widget example above, wrap a map schema with s/constrained:
(def AlertWidget
(s/constrained
{:type (s/enum :info :critical)
:button {(s/optional-key :dismiss) s/Str}}
(fn [{:keys [type button]}]
(or (not= type :info) (some? (:dismiss button))))
'info-requires-dismiss))API docs: https://cljdoc.org/d/prismatic/schema/CURRENT/api/schema.core