#---------------------------------------------------------------------
# TITLE:
#       widgetclass.test
#
# AUTHOR:
#       Arnulf Wiedemann with a lot of code form the snit tests by
#       Will Duquette
#
# DESCRIPTION:
#       Test cases for ::itcl::type command.
#       Uses the ::tcltest:: harness.
#
#       There is at least Tcl 8.6a3 needed
#
#    The tests assume tcltest 2.2
#-----------------------------------------------------------------------

# ### ### ### ######### ######### #########
## Declare the minimal version of Tcl required to run the package
## tested by this testsuite, and its dependencies.

proc testsNeedTcl {version} {
    # This command ensures that a minimum version of Tcl is used to
    # run the tests in the calling testsuite. If the minimum is not
    # met by the active interpreter we forcibly bail out of the
    # testsuite calling the command. The command has to be called
    # immediately after loading the utilities.

    if {[package vsatisfies [package provide Tcl] $version]} return

    puts "    Aborting the tests found in \"[file tail [info script]]\""
    puts "    Requiring at least Tcl $version, have [package present Tcl]."

    # This causes a 'return' in the calling scope.
    return -code return
}

# ### ### ### ######### ######### #########
## Declare the minimum version of Tcltest required to run the
## testsuite.

proc testsNeedTcltest {version} {
    # This command ensure that a minimum version of the Tcltest
    # support package is used to run the tests in the calling
    # testsuite. If the minimum is not met by the loaded package we
    # forcibly bail out of the testsuite calling the command. The
    # command has to be called after loading the utilities. The only
    # command allowed to come before it is 'textNeedTcl' above.

    # Note that this command will try to load a suitable version of
    # Tcltest if the package has not been loaded yet.

    if {[lsearch [namespace children] ::tcltest] == -1} {
	if {![catch {
	    package require tcltest $version
	}]} {
	    namespace import -force ::tcltest::*
	    return
	}
    } elseif {[package vcompare [package present tcltest] $version] >= 0} {
	return
    }

    puts "    Aborting the tests found in [file tail [info script]]."
    puts "    Requiring at least tcltest $version, have [package present tcltest]"

    # This causes a 'return' in the calling scope.
    return -code return
}

# Set up for Tk tests: enter the event loop long enough to catch
# any bgerrors.
proc tkbide {{msg "tkbide"} {msec 500}} {
    set ::bideVar 0
    set ::bideError ""
    set ::bideErrorInfo ""
    # It looks like update idletasks does the job.
    if {0} {
        after $msec {set ::bideVar 1}
        tkwait variable ::bideVar
    }
    update idletasks
    if {"" != $::bideError} {
        error "$msg: $::bideError" $::bideErrorInfo
    }
}



testsNeedTcl     8.6
testsNeedTcltest 2.2

interp alias {} type {} ::itcl::type
interp alias {} widget {} ::itcl::widget

# Marks tests which are only for Tk.
tcltest::testConstraint tk [expr {![catch {package require Tk}]}]

::tcltest::loadTestedCommands
package require itcl

#-----------------------------------------------------------------------
# Widgets

# A widget is just a widgetadaptor with an automatically created hull
# component (a Tk frame).  So the widgetadaptor tests apply; all we
# need to test here is the frame creation.

test widget-1.1 {creating a widget
} -constraints {
    tk
} -body {
    widget myframe {
        delegate method * to itcl_hull
        delegate option * to itcl_hull
    }

    myframe create .frm -background green

    set a [.frm cget -background]
    set b [.frm itcl_hull]

    destroy .frm
    tkbide
    list $a $b
} -cleanup {
    myframe destroy
} -result {green ::itcl::internal::widgets::hull1.frm}

test widget-2.1 {can't redefine hull
} -constraints {
    tk
} -body {
    # there is no need to define or set itcl_hull as that is done automatically
    widget myframe {
        method resethull {} {
	set itcl_hull ""
	}
    }

    myframe .frm

    .frm resethull
} -returnCodes {
    error
} -cleanup {
    myframe destroy
} -result {can't set "itcl_hull": The itcl_hull component cannot be redefined}


#-----------------------------------------------------------------------
# install
#
# The install command is used to install widget components, while getting
# options for the option database.

test install-1.1 {installed components are created properly
} -constraints {
    tk
} -body {
    widget myframe {
        # Delegate an option just to make sure the component variable
        # exists.
        delegate option -font to text

        constructor {args} {
            installcomponent text using text $win.text -background green
        }

        method getit {} {
            $win.text cget -background
        }
    }

    myframe .frm
    set a [.frm getit]
    destroy .frm
    tkbide
    set a
} -cleanup {
    myframe destroy
} -result {green}

test install-1.2 {installed components are saved properly
} -constraints {
    tk
} -body {
    widget myframe {
        # Delegate an option just to make sure the component variable
        # exists.
        delegate option -font to text

        constructor {args} {
            installcomponent text using text $win.text -background green
        }

        method getit {} {
            $text cget -background
        }
    }

    myframe .frm
    set a [.frm getit]
    destroy .frm
    tkbide
    set a
} -cleanup {
    myframe destroy
} -result {green}

test install-1.4 {install queries option database
} -constraints {
    tk
} -body {
    widget myframe {
        delegate option -font to text

        typeconstructor {
            option add *Myframe.font Courier
        }

        constructor {args} {
            installcomponent text using text $win.text 
        }
    }

    myframe .frm
    set a [.frm cget -font]
    destroy .frm
    tkbide
    set a
} -cleanup {
    myframe destroy
} -result {Courier}

test install-1.5 {explicit options override option database
} -constraints {
    tk
} -body {
    widget myframe {
        delegate option -font to text

        typeconstructor {
            option add *Myframe.font Courier
        }

        constructor {args} {
            installcomponent text using text $win.text -font Times
        }
    }

    myframe .frm
    set a [.frm cget -font]
    destroy .frm
    tkbide
    set a
} -cleanup {
    myframe destroy
} -result {Times}

test install-1.6 {option db works with targetted options
} -constraints {
    tk
} -body {
    widget myframe {
        delegate option -textfont to text as -font

        typeconstructor {
            option add *Myframe.textfont Courier
        }

        constructor {args} {
            installcomponent text using text $win.text
        }
    }

    myframe .frm
    set a [.frm cget -textfont]
    destroy .frm
    tkbide
    set a
} -cleanup {
    myframe destroy
} -result {Courier}

test install-1.8 {install can install non-widget components
} -constraints {
    tk
} -body {
    type dog {
        option -tailcolor black
    }

    widget myframe {
        delegate option -tailcolor to thedog

        typeconstructor {
            option add *Myframe.tailcolor green
        }

        constructor {args} {
            installcomponent thedog using dog $win.dog
        }
    }

    myframe .frm
    set a [.frm cget -tailcolor]
    destroy .frm
    tkbide
    set a

} -cleanup {
    dog destroy
    myframe destroy
} -result {green}

test install-1.9 {ok if no options are delegated to component
} -constraints {
    tk
} -body {
    type dog {
        option -tailcolor black
    }

    widget myframe {
        constructor {args} {
            installcomponent thedog using dog $win.dog
        }
    }

    myframe .frm
    destroy .frm
    tkbide

    # Test passes if no error is raised.
    list ok
} -cleanup {
    myframe destroy
    dog destroy
} -result {ok}

test install-2.1 {
    delegate option * for a non-shadowed option.  The text widget's
    -foreground and -font options should be set according to what's
    in the option database on the widgetclass.
} -constraints {
    tk
} -body {
    widget myframe {
        delegate option * to text

        typeconstructor {
            option add *Myframe.foreground red
            option add *Myframe.font {Times 14}
        }

        constructor {args} {
            installcomponent text using text $win.text
        }
    }

    myframe .frm
    set a [.frm cget -foreground]
    set b [.frm cget -font]
    destroy .frm
    tkbide

    list $a $b
} -cleanup {
    myframe destroy
} -result {red {Times 14}}


test install-2.2 {
    Delegate option * for a shadowed option.  Foreground is declared
    as a non-delegated option, hence it will pick up the option database
    default.  -foreground is not included in the "delegate option *", so
    the text widget's -foreground option will not be set from the
    option database.
} -constraints {
    tk
} -body {
    widget myframe {
        option -foreground white
        delegate option * to text

        typeconstructor {
            option add *Myframe.foreground red
        }

        constructor {args} {
            installcomponent text using text $win.text
        }

        method getit {} {
            $text cget -foreground
        }
    }

    myframe .frm
    set a [.frm cget -foreground]
    set b [.frm getit]
    destroy .frm
    tkbide

    expr {![string equal $a $b]}
} -cleanup {
    myframe destroy
} -result {1}

test install-2.3 {
    Delegate option * for a creation option.  Because the text widget's
    -foreground is set explicitly by the constructor, that always
    overrides the option database.
} -constraints {
    tk
} -body {
    widget myframe {
        delegate option * to text

        typeconstructor {
            option add *Myframe.foreground red
        }

        constructor {args} {
            installcomponent text using text $win.text -foreground blue
        }
    }

    myframe .frm
    set a [.frm cget -foreground]
    destroy .frm
    tkbide

    set a
} -cleanup {
    myframe destroy
} -result {blue}

test install-2.4 {
    Delegate option * with an excepted option.  Because the text widget's
    -state is excepted, it won't be set from the option database.
} -constraints {
    tk
} -body {
    widget myframe {
        delegate option * to text except -state

        typeconstructor {
            option add *Myframe.foreground red
            option add *Myframe.state disabled
        }

        constructor {args} {
            installcomponent text using text $win.text
        }

        method getstate {} {
            $text cget -state
        }
    }

    myframe .frm
    set a [.frm getstate]
    destroy .frm
    tkbide

    set a
} -cleanup {
    myframe destroy
} -result {normal}


#-----------------------------------------------------------------------
# Advanced installhull tests
#
# installhull is used to install the hull widget for both widgets and
# widget adaptors.  It has two forms.  In one form it installs a widget
# created by some third party; in this form no querying of the option
# database is needed, because we haven't taken responsibility for creating
# it.  But in the other form (installhull using) installhull actually
# creates the widget, and takes responsibility for querying the
# option database as needed.
#
# NOTE: "installhull using" is always used to create a widget's hull frame.
#
# That options passed into installhull override those from the
# option database.

test installhull-1.1 {
    options delegated to a widget's itcl_hull frame with the same name are
    initialized from the option database.  Note that there's no
    explicit code in Snit to do this; it happens because we set the
    -class when the widget was created.  In fact, it happens whether
    we delegate the option name or not.
} -constraints {
    tk
} -body {
    widget myframe {
        delegate option -background to itcl_hull

        typeconstructor {
            option add *Myframe.background red
            option add *Myframe.width 123
        }

        method getwid {} {
            $itcl_hull cget -width
        }
    }

    myframe .frm
    set a [.frm cget -background]
    set b [.frm getwid]
    destroy .frm
    tkbide
    list $a $b
} -cleanup {
    myframe destroy
} -result {red 123}

test installhull-1.2 {
    Options delegated to a widget's itcl_hull frame with a different name are
    initialized from the option database.
} -constraints {
    tk
} -body {
    widget myframe {
        delegate option -mainbackground to itcl_hull as -background

        typeconstructor {
            option add *Myframe.mainbackground green
        }
    }

    myframe .frm
    set a [.frm cget -mainbackground]
    destroy .frm
    tkbide
    set a
} -cleanup {
    myframe destroy
} -result {green}



test option-5.1 {local widget options read from option database
} -constraints {
    tk
} -body {
    widget dog {
        option -foo a
        option -bar b

        typeconstructor {
            option add *Dog.bar bb
        }
    }

    dog .fido
    set a [.fido cget -foo]
    set b [.fido cget -bar]
    destroy .fido
    tkbide

    list $a $b

} -cleanup {
    dog destroy
} -result {a bb}

test option-5.2 {local option database values available in constructor
} -constraints {
    tk
} -body {
    widget dog {
        option -bar b
        variable saveit

        typeconstructor {
            option add *Dog.bar bb
        }

        constructor {args} {
            set saveit $itcl_options(-bar)
        }

        method getit {} {
            return $saveit
        }
    }

    dog .fido
    set result [.fido getit]
    destroy .fido
    tkbide

    set result
} -cleanup {
    dog destroy
} -result {bb}

#-----------------------------------------------------------------------
# Setting the widget class explicitly

test widgetclass-1.3 {widgetclass must begin with uppercase letter
} -constraints {
    tk
} -body {
    widget dog {
        widgetclass dog
    }
} -returnCodes {
    error
} -result {widgetclass "dog" does not begin with an uppercase letter}

test widgetclass-1.4 {widgetclass can only be defined once
} -constraints {
    tk
} -body {
    widget dog {
        widgetclass Dog
        widgetclass Dog
    }
} -returnCodes {
    error
} -result {too many widgetclass statements}

test widgetclass-1.5 {widgetclass set successfully
} -constraints {
    tk
} -body {
    widget dog {
        widgetclass DogWidget
    }

    # The test passes if no error is thrown.
    list ok
} -cleanup {
    dog destroy
} -result {ok}

test widgetclass-1.6 {implicit widgetclass applied to hull
} -constraints {
    tk
} -body {
    widget dog {
        typeconstructor {
            option add *Dog.background green
        }

        method background {} {
            $itcl_hull cget -background
        }
    }

    dog .dog

    set bg [.dog background]

    destroy .dog

    set bg
} -cleanup {
    dog destroy
} -result {green}

test widgetclass-1.7 {explicit widgetclass applied to hull
} -constraints {
    tk
} -body {
    widget dog {
        widgetclass DogWidget

        typeconstructor {
            option add *DogWidget.background yellow
        }

        method background {} {
            $itcl_hull cget -background
        }
    }

    dog .dog

    set bg [.dog background]

    destroy .dog

    set bg
} -cleanup {
    dog destroy
} -result {yellow}

#-----------------------------------------------------------------------
# hulltype statement

test hulltype-1.3 {hulltype can be frame
} -constraints {
    tk
} -body {
    widget dog {
        delegate option * to itcl_hull
        hulltype frame
    }

    dog .fido
    catch {.fido configure -use} result
    destroy .fido
    tkbide

    set result
} -cleanup {
    dog destroy
} -result {unknown option "-use"}

test hulltype-1.4 {hulltype can be toplevel
} -constraints {
    tk
} -body {
    widget dog {
        delegate option * to itcl_hull
        hulltype toplevel
    }

    dog .fido
    catch {.fido configure -use} result
    destroy .fido
    tkbide

    set result
} -cleanup {
    dog destroy
} -result {-use use Use {} {}}

test hulltype-1.5 {hulltype can only be defined once
} -constraints {
    tk
} -body {
    widget dog {
        hulltype frame
        hulltype toplevel
    }
} -returnCodes {
    error
} -result {too many hulltype statements}

test hulltype-2.1 {list of valid hulltypes
} -constraints {
    tk
} -body {
    type dog {
    }

    lsort [dog info hulltypes]
} -cleanup {
    dog destroy
} -result {frame labelframe toplevel ttk:frame ttk:labelframe ttk:toplevel}

test winfo-10.1 {widget info widgets
} -constraints {
    tk
} -body {
    widget dog {
    }

    widget cat {
    }

    lsort [dog info widgets]
} -cleanup {
    dog destroy
    cat destroy
} -result {cat dog}

test winfo-10.2 {widget info components
} -constraints {
    tk
} -body {
    widget dog {
        component comp1
        component comp2
    }

    widget cat {
        component comp1
        component comp1a
    }

    set a [lsort [dog info components]]
    set b [lsort [cat info components]]
    list $a $b
} -cleanup {
    dog destroy
    cat destroy
} -result {{comp1 comp2 itcl_hull} {comp1 comp1a itcl_hull}}

test winfo-10.3 {widget info widgetclasses
} -constraints {
    tk
} -body {
    widget dog {
        widgetclass DogWidget
    }

    widget cat {
        widgetclass CatWidget
    }

    lsort [dog info widgetclasses]
} -cleanup {
    dog destroy
    cat destroy
} -result {CatWidget DogWidget}


#---------------------------------------------------------------------
# Clean up

if {[llength [::info command ::itcl::dumppreserveinfo]] > 0} {
    ::itcl::finish
    ::itcl::dumppreserveinfo  checkmemoryleaks
    ::itcl::dumprefcountinfo -nodeleted
}

::tcltest::cleanupTests
return
