;; This Source Code Form is subject to the terms of the Mozilla Public ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; bjogui/tests/windows.bjo — windows besides the main one. ;; ;; bjo run tests/windows.bjo ;; ;; Avalonia runs with no screen, so the windows are real but not seen. A ;; click on a window's close button is made as the platform makes it. (import (std prelude)) (import (std simpletest)) (import (std interop)) (import (bjogui view)) (import (bjogui app)) (import/class (Object (: System.Object)) (Control (: Avalonia.Controls.Control)) (Window (: Avalonia.Controls.Window)) (TopLevel (: Avalonia.Controls.TopLevel)) (Visual (: Avalonia.Visual)) (TextBox (: Avalonia.Controls.TextBox)) (TextBlock (: Avalonia.Controls.TextBlock)) (InputElement (: Avalonia.Input.InputElement)) (Interactive (: Avalonia.Interactivity.Interactive)) (RoutedEvent (: Avalonia.Interactivity.RoutedEvent)) (RoutedEventArgs (: Avalonia.Interactivity.RoutedEventArgs (-> RoutedEvent RoutedEventArgs))) (CloseReason (: Avalonia.Controls.WindowCloseReason)) (ClrType (: System.Type)) (PropertyInfo (: System.Reflection.PropertyInfo))) (import/extern (top-level-of (: Avalonia.Controls.TopLevel.GetTopLevel (-> Visual TopLevel))) (title-of (: Avalonia.Controls.Window.Title (-> Window string) #:get)) (visible? (: Avalonia.Visual.IsVisible (-> Visual bool) #:get)) (box-text! (: Avalonia.Controls.TextBox.Text (-> TextBox string void) #:set)) (block-text (: Avalonia.Controls.TextBlock.Text (-> TextBlock string) #:get)) (focused? (: Avalonia.Input.InputElement.IsFocused (-> InputElement bool) #:get)) (click-event (: Avalonia.Controls.Button.ClickEvent #:get)) (raise-event (: Avalonia.Interactivity.Interactive.RaiseEvent (-> Interactive RoutedEventArgs void))) ;; What a click on the close button calls: the window's platform's Closing. ;; Avalonia hides it from C#, so it is found by reflection. (type-named (: System.Type.GetType (-> string ClrType))) (property-of (: System.Type.GetProperty (-> ClrType string PropertyInfo))) (value-of (: System.Reflection.PropertyInfo.GetValue (-> PropertyInfo Object Object))) (invoke (: System.Delegate.DynamicInvoke (-> Delegate (Array Object) Object))) (sleep-ms (: System.Threading.Thread.Sleep (-> int void) #:blocking))) (type (: Msg (Union OpenSettings CloseSettings OpenNotes CloseNotes (: Name string) Save FocusName))) (type (: Model (Record (: settings bool) (: notes bool) (: name string) (: saved (List string))))) (: (-> Msg Model (Tuple Model (List (Event Msg))))) (defun (gui-update msg m) (match msg (OpenSettings (next (record-set m (settings #t)))) (CloseSettings (next (record-set m (settings #f)))) (OpenNotes (next (record-set m (notes #t)))) (CloseNotes (next (record-set m (notes #f)))) ((Name s) (next (record-set m (name s)))) (Save (next (record-set m (saved (cons (.name m) (.saved m)))))) (FocusName (next m (focus "name"))))) (: (-> Model (View Msg))) (defun (view m) (text #"name: ${(.name m)}")) (: (-> Model (List (WindowView Msg)))) (defun (windows m) (def settings (window "settings" "Settings" CloseSettings (column (keyed "name" (text-input (.name m) Name)) ; 0 (button "Save" Save)) ; 1 #:modal #t)) (def notes (window "notes" #"Notes: ${(.name m)}" CloseNotes (text (.name m)) #:width 200.0 #:height 100.0)) (list-append (if (.settings m) (list settings) '()) (if (.notes m) (list notes) '()))) (defun (main) (start-headless!) (def app (start-app (Model (settings #f) (notes #f) (name "") (saved '())) gui-update view (fun (c) unit) #:windows windows)) (defun (in-window key path) (app-window-control app key path)) (defun (frame key) (cast Window (top-level-of (cast Visual (in-window key '()))))) (defun (click c) (raise-event (cast Interactive c) (RoutedEventArgs. (cast RoutedEvent click-event)))) (: (-> (-> bool) bool)) (defun (wait-for done?) (let go ((tries 0)) (app-run-pending! app) (cond ((done?) #t) ((> tries 200) #f) (else (sleep-ms 10) (go (+ tries 1)))))) (expect "no windows at first" (app-windows app) '()) ;; --- opening, and the window's own view --------------------------------------- (app-dispatch! app OpenSettings) (expect "a window is opened" (app-windows app) '("settings")) (def settings (frame "settings")) (expect "with its title" (title-of settings) "Settings") (expect "and shown" (visible? (cast Visual settings)) #t) (box-text! (cast TextBox (in-window "settings" '(0))) "Ada") (expect "its controls send messages" (.name (app-model app)) "Ada") (expect "which the main view shows" (block-text (cast TextBlock (app-control app))) "name: Ada") (click (in-window "settings" '(1))) (expect "a button in it" (.saved (app-model app)) '("Ada")) ;; --- two windows, and changes --------------------------------------------------- (app-dispatch! app OpenNotes) (expect "a second window" (app-windows app) '("settings" "notes")) (expect "the first is the same window" (eq? (cast Object (frame "settings")) (cast Object settings)) #t) (expect "a title from the model" (title-of (frame "notes")) "Notes: Ada") (app-dispatch! app (Name "Bob")) (expect "changes with it" (title-of (frame "notes")) "Notes: Bob") (expect "and so does its view" (block-text (cast TextBlock (in-window "notes" '()))) "Bob") ;; --- a command finds a control in a window --------------------------------------- (app-dispatch! app FocusName) (expect "focus in a window" (wait-for (fun () (focused? (cast InputElement (in-window "settings" '(0)))))) #t) ;; --- closing ---------------------------------------------------------------------- (app-dispatch! app CloseSettings) (expect "a window the model has no more is closed" (app-windows app) '("notes")) (expect "and not shown" (visible? (cast Visual settings)) #f) (def notes (frame "notes")) (defun (impl-of (: o Object) type-name property) (value-of (property-of (type-named type-name) property) o)) (def platform (impl-of (cast Object notes) "Avalonia.Controls.TopLevel, Avalonia.Controls" "PlatformImpl")) (def closing (impl-of platform "Avalonia.Platform.IWindowImpl, Avalonia.Controls" "Closing")) (ignore (invoke (cast Delegate closing) (: (make-array 1 #:fill (cast Object CloseReason.WindowClosing)) (Array Object)))) (expect "a click on the close button does not close it" (visible? (cast Visual notes)) #t) (expect "it sends on-close" (wait-for (fun () (not (.notes (app-model app))))) #t) (expect "and the model closes it" (app-windows app) '()) (expect "so it is gone" (visible? (cast Visual notes)) #f) ;; --- stopping closes them --------------------------------------------------------- (app-dispatch! app OpenNotes) (def again (frame "notes")) (app-stop! app) (expect "a stopped app closes its windows" (visible? (cast Visual again)) #f) (println "bjogui windows ok"))