Skip to content

Commit

Permalink
(red) proto1 test is correctly detecting lack of implementation for
Browse files Browse the repository at this point in the history
detecting whether an offer is selected or not.

unit1 test (green) is correctly detecting a partial implementation though.
  • Loading branch information
MaxWilson committed Jan 6, 2024
1 parent 3878da5 commit 39776de
Showing 1 changed file with 74 additions and 15 deletions.
89 changes: 74 additions & 15 deletions test/Chargen.Accept.fs
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,33 @@ type 't Output = 't * MenuOutput
type 't ListOutput = ('t list) Output
type 't OptionOutput = ('t option) Output

type Key = string
type KeySegment = string
type 't ReversedList = 't list
type Key = KeySegment ReversedList
type OfferConfig = {
key: KeySegment option
label: string option
}
with static member blank = { key = None; label = None }
type OfferInput = {
selected: Set<Key>
prefix: KeySegment ReversedList
}
with static member fresh = { selected = Set.empty }
type 't Offer = Offer of (OfferInput -> 't)
with
static member fresh = { selected = Set.empty; prefix = [] }
member input.fullKey config =
input.fullKey config.key
member input.fullKey (segment: KeySegment option) =
match segment with Some k -> k::input.prefix | None -> input.prefix
member input.extend (config: OfferConfig) = { input with prefix = input.fullKey config }
member input.extend (segment: KeySegment option) = { input with prefix = input.fullKey segment }

type 't Offer = { config: OfferConfig; func: (OfferConfig -> OfferInput -> 't * MenuOutput) }
with
member this.recur input = this.func this.config input
type 't ListOffer = ('t list) Offer
type 't OptionOffer = ('t option) Offer

type OfferConfig = {
key: Key option
label: string option
}
with static member blank = { key = None; label = None }
open type OfferConfig

type 'reactElement RenderApi = {
Expand Down Expand Up @@ -71,15 +84,55 @@ let render (render: 'reactElement RenderApi) (menus: MenuOutput list) =
menus |> List.map (recur true render.unconditional) |> render.combine

type Op =
static member skill v: 't OptionOffer = notImpl()
static member trait' v: 't OptionOffer = notImpl()
static member offer(config, func) = { config = config; func = func }
static member offer func = Op.offer(OfferConfig.blank, func)

static member skill (name: string, level: int): _ OptionOffer =
Op.skill({ OfferConfig.blank with label = Some $"{name} %+d{level}" }, (name, [level]))
static member skill (name: string, levels: int list): _ OptionOffer =
Op.skill(OfferConfig.blank, (name, levels))
static member skill (config, (name: string, levels: int list)): _ OptionOffer =
Op.offer(config, fun config input -> None, (Leaf (defaultArg config.label (toString name))))

static member trait' (v: 't): 't OptionOffer =
Op.trait'({ OfferConfig.blank with key = Some (toString v) }, v)
static member trait' (config, v): 't OptionOffer =
Op.offer(config, fun config input -> Some v, (Leaf (defaultArg config.label (toString v))))

static member budgeted v: 't ListOffer = notImpl()
static member either v : 't OptionOffer = notImpl()

static member either options : 't OptionOffer =
Op.either(OfferConfig.blank, options)
static member either (config, options: 't OptionOffer list) : 't OptionOffer =
Op.offer(
config,
fun config input ->
let children = [
for o in options do
let (value, menu) = o.recur input
let key = o.config.key |> Option.orElse o.config.label
let fullKey = input.fullKey key
let selected = key.IsSome && input.selected.Contains fullKey
value, (selected, menu)
]
let selectedValue = children |> List.tryPick fst // if this were eitherN we'd return them all but since it's regular either we return the first one, if any
let childMenus = children |> List.map snd
selectedValue, Either(config.label, childMenus)
)

static member and' v : 't OptionOffer = notImpl()
static member eitherN v : 't ListOffer = notImpl()
static member andN' v : 't ListOffer = notImpl()
static member promote (o: 't OptionOffer): 't ListOffer = notImpl()
static member evaluate (state: OfferInput) (offers: _ Offer list) = notImpl()
static member promote (o: 't OptionOffer): 't ListOffer =
Op.offer(
o.config,
fun config input ->
let (v, menu) = o.recur input
List.ofOption v, menu
)
static member evaluate (state: OfferInput) (offer: _ Offer) =
offer.recur state

let newKey txt = $"{txt}-{System.Guid.NewGuid()}"
let label txt = { blank with label = Some txt }
open type Op
Expand Down Expand Up @@ -107,7 +160,7 @@ let swash(): Trait' ListOffer list = [
skill("Acrobatics", [1..3])
])
let mainWeapons = ["Rapier"; "Broadsword"; "Polearm"; "Two-handed sword"] |> List.map (fun name -> name, newKey name)
let weaponsAt (bonus: int) = mainWeapons |> List.map (fun (name, key) -> skill({ blank with key = Some key }, name, bonus))
let weaponsAt (bonus: int) = mainWeapons |> List.map (fun (name, key) -> skill({ blank with key = Some key }, (name, [bonus])))
eitherN [
either(label "Sword!", weaponsAt +5) |> promote
andN'(label "Sword and Dagger", [either(weaponsAt +4); skill("Main-gauche", +1)])
Expand Down Expand Up @@ -155,8 +208,14 @@ let pseudoReactApi = {
combine = Fragment
}

[<Tests>]
let unit1 = testCase "Unit.Chargen.unit1" <| fun () ->
test <@ either[trait' "Fight"; trait' "Hide"] |> evaluate OfferInput.fresh |> snd = Either(None, [false, Leaf "Fight"; false, Leaf "Hide"]) @>
let selectFight = { OfferInput.fresh with selected = Set.ofList [["Fight"]] }
test <@ either[trait' "Fight"; trait' "Hide"] |> evaluate selectFight |> snd = Either(None, [true, Leaf "Fight"; false, Leaf "Hide"]) @>

let proto1 = testCase "proto1" <| fun () ->
let actual = swash() |> evaluate OfferInput.fresh // shouldn't actually use OfferInput.fresh here. Need to pick the options we want to show up in pseudoActual.s
let actual = swash() |> List.map (evaluate OfferInput.fresh >> snd) // shouldn't actually use OfferInput.fresh here. Need to pick the options we want to show up in pseudoActual.s
let pseudoActual = // pseudo-actual because actual will be created from templates + OfferInput (i.e. selected keys), not hardwired as Menus, but that's still TODO
let menus = [
Leveled("Climbing", 1)
Expand Down

0 comments on commit 39776de

Please sign in to comment.