# -*-Tcl-*-
#---------------------------------------------------------------------
# TITLE:
#	snit.test
#
# AUTHOR:
#	Will Duquette
#
# DESCRIPTION:
#	Test cases for snit.tcl.  Uses the ::tcltest:: harness.

# Note:
#    Snit assumes Tcl 8.4
#    The tests assume tcltest 2.1

#---------------------------------------------------------------------
# Load the tcltest package, initialize some constraints.

if {![package vsatisfies [package provide Tcl] 8.4]} {
    puts "Aborting tests for snit."
    puts "Requiring Tcl 8.4, have [package present Tcl]"
    return
}

if {[lsearch [namespace children] ::tcltest] == -1} {
    package require tcltest 2.1
    namespace import ::tcltest::*
} else {
    # Ensure that 2.1 or higher present.

    if {![package vsatisfies [package present tcltest] 2.1]} {
	puts "Aborting tests for snit."
	puts "Requiring tcltest 2.1, have [package present tcltest]"
	return
    }
}

if { [lsearch $auto_path [file dirname [info script]]] == -1 } {
    set auto_path [linsert $auto_path 0 [file dirname [info script]]]
}

set ::tcltest::testConstraints(tk) [info exists tk_version]

if {$::tcltest::testConstraints(tk) && 
    ![catch {package require BWidget} result]} {
    set ::tcltest::testConstraints(bwidget) 1
} else {
    set ::tcltest::testConstraints(bwidget) 0
}

#---------------------------------------------------------------------
# Load the snit package.

package forget snit
catch {namespace delete snit}
if {[catch {source [file join [file dirname [info script]] snit.tcl]} msg]} {
    puts "skipped [file tail [info script]]: $msg"
    return
}

puts "- Tcl  [package present Tcl]"
puts "- snit [package present snit]"

namespace import ::snit::*

# Set up for Tk tests: Repeat background errors
proc bgerror {msg} {
    global errorInfo
    set ::bideError $msg
    set ::bideErrorInfo $errorInfo
}

# 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
    }
}

# cleanup type
proc cleanupType {name} {
    if {[namespace exists $name]} {
        if {[catch {$name destroy} result]} {
            global errorInfo
            puts $errorInfo
            error "Could not cleanup $name!"
        }
    }
    tkbide "cleanupType $name"
}

# cleanup before each test
proc cleanup {} {
    global errorInfo

    cleanupType ::dog
    cleanupType ::cat
    cleanupType ::mylabel
    cleanupType ::myframe
    cleanupType ::foo
    cleanupType ::bar
    cleanupType ::tail
    cleanupType ::papers
    cleanupType ::animal
    cleanupType ::confused-dog
    catch {option clear}

    if {[info commands "spot"] ne ""} {
        puts "spot not erased!"
        error "spot not erased!"
    }

    if {[info commands "fido"] ne ""} {
        puts "fido not erased!"
        error "fido not erased!"
    }
}


#-----------------------------------------------------------------------
# Internals: tests for Snit utility functions

test Expand-1.1 {template, no arguments} -body {
    snit::Expand "My %TEMPLATE%"
} -result {My %TEMPLATE%}

test Expand-1.2 {template, no matching arguments} -body {
    snit::Expand "My %TEMPLATE%" %FOO% foo 
} -result {My %TEMPLATE%}

test Expand-1.3 {template with matching arguments} -body {
    snit::Expand "%FOO% %BAR% %FOO%" %FOO% bar %BAR% foo
} -result {bar foo bar}

test Expand-1.4 {template with odd number of arguments} -body {
    snit::Expand "%FOO% %BAR% %FOO%" %FOO% 
} -result {char map list unbalanced} -returnCodes error

test Mappend-1.1 {template, no arguments} -body {
    set text "Prefix: "
    snit::Mappend text "My %TEMPLATE%"
} -result {Prefix: My %TEMPLATE%} -cleanup {
    unset text
}

test Mappend-1.2 {template, no matching arguments} -body {
    set text "Prefix: "
    snit::Mappend text "My %TEMPLATE%" %FOO% foo 
} -result {Prefix: My %TEMPLATE%} -cleanup {
    unset text
}

test Mappend-1.3 {template with matching arguments} -body {
    set text "Prefix: "
    snit::Mappend text "%FOO% %BAR% %FOO%" %FOO% bar %BAR% foo
} -result {Prefix: bar foo bar} -cleanup {
    unset text
}

test Mappend-1.4 {template with odd number of arguments} -body {
    set text "Prefix: "
    snit::Mappend text "%FOO% %BAR% %FOO%" %FOO% 
} -result {char map list unbalanced} -returnCodes error -cleanup {
    unset text
}

test RT.UniqueName-1.1 {no name collision} -body {
    set counter 0

    # Standard qualified type name.
    set n1 [snit::RT.UniqueName counter ::mytype ::my::%AUTO%]

    # Standard qualified widget name.
    set n2 [snit::RT.UniqueName counter ::mytype .my.%AUTO%]

    list $n1 $n2
} -result {::my::mytype1 .my.mytype2} -cleanup {
    unset counter n1 n2
}

test RT.UniqueName-1.2 {name collision} -body {
    set counter 0

    # Create the first two equivalent procs.
    proc ::mytype1 {} {}
    proc ::mytype2 {} {}

    # Create a new name; it should skip to 3.
    snit::RT.UniqueName counter ::mytype ::%AUTO%
} -result {::mytype3} -cleanup {
    unset counter
    rename ::mytype1 ""
    rename ::mytype2 ""
}

test RT.UniqueName-1.3 {nested type name} -body {
    set counter 0

    snit::RT.UniqueName counter ::thisis::yourtype ::your::%AUTO%
} -result {::your::yourtype1} -cleanup {
    unset counter
}

test RT.UniqueInstanceNamespace-1.1 {no name collision} -setup {
    namespace eval ::mytype:: {}
} -body {
    set counter 0
    snit::RT.UniqueInstanceNamespace counter ::mytype
} -result {::mytype::Snit_inst1} -cleanup {
    unset counter
    namespace delete ::mytype::
}

test RT.UniqueInstanceNamespace-1.2 {name collision} -setup {
    namespace eval ::mytype:: {}
    namespace eval ::mytype::Snit_inst1:: {}
    namespace eval ::mytype::Snit_inst2:: {}
} -body {
    set counter 0
    
    # Should skip to 3.
    snit::RT.UniqueInstanceNamespace counter ::mytype
} -result {::mytype::Snit_inst3} -cleanup {
    unset counter
    namespace delete ::mytype::
}

test Contains-1.1 {contains element} -setup {
    set mylist {foo bar baz}
} -body {
    snit::Contains baz $mylist
} -result {1} -cleanup {
    unset mylist
}

test Contains-1.2 {does not contain element} -setup {
    set mylist {foo bar baz}
} -body {
    snit::Contains quux $mylist
} -result {0} -cleanup {
    unset mylist
}

#-----------------------------------------------------------------------
# type compilation

# snit::compile returns two values, the qualified type name
# and the script to execute to define the type.  This section
# only checks the length of the list and the type name;
# the content of the script is validated by the remainder
# of this test suite.

test compile-1.1 {compile returns qualified type} {} {
    set compResult [compile type dog { }]

    list [llength $compResult] [lindex $compResult 0]
} {2 ::dog}

#-----------------------------------------------------------------------
# type destruction

test typedestruction-1.1 {type command is deleted} {} {
    type dog { }
    dog destroy
    info command ::dog
} {}

test typedestruction-1.2 {instance commands are deleted} {} {
    type dog { }

    dog create spot
    dog destroy
    info command ::spot
} {}

test typedestruction-1.3 {type namespace is deleted} {} {
    type dog { }
    dog destroy
    namespace exists ::dog
} {0}

test typedestruction-1.4 {type proc is destroyed on error} {} {
    catch {type dog {
        error "Error creating dog"
    }} result

    list [namespace exists ::dog] [info command ::dog]
} {0 {}}

#-----------------------------------------------------------------------
# type and typemethods

test type-1.1 {type names get qualified} {} {
    cleanup

    type dog {}
} {::dog}

test type-1.2 {typemethods can be defined} {} {
    cleanup

    type dog {
        typemethod foo {a b} {
            return [list $a $b]
        }
    }

    dog foo 1 2
} {1 2}

test type-1.3 {upvar works in typemethods} {} {
    cleanup

    type dog { 
        typemethod goodname {varname} {
            upvar $varname myvar
            set myvar spot
        }
    }
    
    set thename fido
    dog goodname thename
    set thename
} {spot}

test type-1.4 {typemethod args can't include type} {} {
    cleanup

    catch {
        type dog { 
            typemethod foo {a type b} { }
        }
    } result
    set result
} {typemethod foo's arglist may not contain "type" explicitly}

test type-1.5 {typemethod args can't include self} {} {
    cleanup

    catch {
        type dog { 
            typemethod foo {a self b} { }
        }
    } result
    set result
} {typemethod foo's arglist may not contain "self" explicitly}

test type-1.6 {typemethod args can span multiple lines} {} {
    cleanup

    # This case caused an error at definition time in 0.9 because the
    # arguments were included in a comment in the compile script, and 
    # the subsequent lines weren't commented.
    type dog { 
        typemethod foo {
            a 
            b
        } { }
    }
} {::dog}


#-----------------------------------------------------------------------
# typeconstructor

test typeconstructor-1.1 {a typeconstructor can be defined} {} {
    cleanup

    type dog {
        typevariable a

        typeconstructor {
            set a 1
        }

        typemethod aget {} {
            return $a
        }
    }

    dog aget
} {1}

test typeconstructor-1.2 {only one typeconstructor can be defined} {} {
    cleanup

    catch {
        type dog {
            typevariable a

            typeconstructor {
                set a 1
            }

            typeconstructor {
                set a 2
            }
        }
    } result

    set result
} {too many typeconstructors}

test typeconstructor-1.3 {type proc is destroyed on error} {} {
    catch {
        type dog {
            typeconstructor {
                error "Error creating dog"
            }
        }
    } result

    list [namespace exists ::dog] [info command ::dog]
} {0 {}}

#-----------------------------------------------------------------------
# Type components 

test typecomponent-1.1 {typecomponent defines typevariable} {} {
    cleanup

    catch {
    type dog {
        typecomponent mycomp
            
        typemethod test {} {
            return $mycomp
        }
    }

    dog test
    } result
    set result
} {}

test typecomponent-1.2 {typecomponent trace executes} {} {
    cleanup
    type dog {
        typecomponent mycomp

        typemethod test {} {
            typevariable Snit_typecomponents
            set mycomp foo
            return $Snit_typecomponents(mycomp)
        }
    }

    dog test
} {foo}

test typecomponent-1.3 {typecomponent -public works} {} {
    cleanup
    type dog {
        typecomponent mycomp -public string

        typeconstructor {
            set mycomp string
        }
    }

    dog string length foo
} {3}

test typecomponent-1.4 {typecomponent -inherit yes} {} {
    cleanup
    type dog {
        typecomponent mycomp -inherit yes

        typeconstructor {
            set mycomp string
        }
    }

    dog length foo
} {3}


#-----------------------------------------------------------------------
# hierarchical type methods

test htypemethod1.1 {hierarchical method, two tokens} {} {
    cleanup

    type dog {
        typemethod {wag tail} {} {
            return "wags tail"
        }
    }

    dog wag tail
} {wags tail}

test htypemethod1.2 {hierarchical method, three tokens} {} {
    cleanup

    type dog {
        typemethod {wag tail proudly} {} {
            return "wags tail proudly"
        }
    }

    dog wag tail proudly
} {wags tail proudly}

test htypemethod1.3 {hierarchical method, three tokens} {} {
    cleanup

    type dog {
        typemethod {wag tail really high} {} {
            return "wags tail really high"
        }
    }

    dog wag tail really high
} {wags tail really high}

test htypemethod1.4 {redefinition is OK} {} {
    cleanup

    type dog {
        typemethod {wag tail} {} {
            return "wags tail"
        }
        typemethod {wag tail} {} {
            return "wags tail briskly"
        }
    }

    dog wag tail
} {wags tail briskly}

test htypemethod1.5 {proper error on missing submethod} {} {
    cleanup

    type dog {
        typemethod {wag tail} {} { }
    }

    catch {dog wag} result
    set result
} {wrong number args: should be "::dog wag method args"}


test htypemethod2.1 {prefix/method collision} {} {
    cleanup

    catch {
        type dog {
            typemethod wag {} {}
            typemethod {wag tail} {} {}
        }
    } result

    set result
} {Error in "typemethod {wag tail}...", "wag" has no submethods.}

test htypemethod2.2 {prefix/method collision} {} {
    cleanup

    catch {
        type dog {
            typemethod {wag tail} {} {}
            typemethod wag {} {}
        }
    } result

    set result
} {Error in "typemethod wag...", "wag" has submethods.}

test htypemethod2.3 {prefix/method collision} {} {
    cleanup

    catch {
        type dog {
            typemethod {wag tail} {} {}
            typemethod {wag tail proudly} {} {}
        }
    } result

    set result
} {Error in "typemethod {wag tail proudly}...", "wag tail" has no submethods.}

test htypemethod2.4 {prefix/method collision} {} {
    cleanup

    catch {
        type dog {
            typemethod {wag tail proudly} {} {}
            typemethod {wag tail} {} {}
        }
    } result

    set result
} {Error in "typemethod {wag tail}...", "wag tail" has submethods.}

#-----------------------------------------------------------------------
# Typemethod delegation

test dtypemethod-1.1 {delegate typemethod to non-existent component} {} {
    cleanup

    set result ""

    type dog {
        delegate typemethod foo to bar
    }

    catch {dog foo} result
    set result
} {::dog delegates typemethod "foo" to undefined typecomponent "bar"}

test dtypemethod-1.2 {delegating to existing typecomponent} {
    cleanup

    type dog {
        delegate typemethod length to string

        typeconstructor {
            set string string
        }
    }

    dog length foo
} {3}

test dtypemethod-1.3 {delegating to existing typecomponent with error} {
    cleanup

    type dog {
        delegate typemethod length to string

        typeconstructor {
            set string string
        }
    }

    set result ""
    catch {dog length foo bar} result
    set result
} {wrong # args: should be "string length string"}

test dtypemethod-1.4 {delegating unknown typemethods to existing typecomponent} {
    cleanup

    type dog {
        delegate typemethod * to string

        typeconstructor {
            set string string
        }
    }

    dog length foo
} {3}

test dtypemethod-1.5 {delegating unknown typemethod to existing typecomponent with error} {
    cleanup

    type dog {
        delegate typemethod * to stringhandler

        typeconstructor {
            set stringhandler string
        }
    }

    set result ""
    catch {dog foo bar} result
    set result
} {bad option "foo": must be bytelength, compare, equal, first, index, is, last, length, map, match, range, repeat, replace, tolower, toupper, totitle, trim, trimleft, trimright, wordend, or wordstart}


test dtypemethod-1.6 {can't delegate local typemethod: order 1} {
    cleanup

    catch {
        type dog {
            typemethod foo {} {}
            delegate typemethod foo to bar
        }
    } result

    set result
} {Error in "delegate typemethod foo...", "foo" has been defined locally.}

test dtypemethod-1.7 {can't delegate local typemethod: order 2} {
    cleanup

    catch {
        type dog {
            delegate typemethod foo to bar
            typemethod foo {} {}
        }
    } result

    set result
} {Error in "typemethod foo...", "foo" has been delegated}

test dtypemethod-1.8 {excepted methods are caught properly} {
    cleanup

    type dog {
        delegate typemethod * to string except {match index}

        typeconstructor {
            set string string
        }
    }

    catch {dog length foo} a
    catch {dog match foo}  b
    catch {dog index foo}  c
    
    list $a $b $c
} {3 {"::dog match" is not defined} {"::dog index" is not defined}}

test dtypemethod-1.9 {as clause can include arguments} {
    cleanup

    proc tail {a b} {
        return "<$a $b>"
    }

    type dog {
        delegate typemethod wag to tail as {wag briskly}

        typeconstructor {
            set tail tail
        }
    }

    dog wag
} {<wag briskly>}

test dtypemethod-2.1 {'using "%c %m"' gets normal behavior} {
    cleanup

    type dog {
        delegate typemethod length to string using {%c %m}

        typeconstructor {
            set string string
        }
    }

    dog length foo
} {3}

test dtypemethod-2.2 {All relevant 'using' conversions are converted} {
    cleanup

    proc echo {args} { 
        return $args 
    }

    type dog {
        delegate typemethod {tail wag} using {echo %% %t %M %m %j %n %w %s %c}
    }

    dog tail wag
} {% ::dog {tail wag} wag tail_wag %n %w %s %c}

test dtypemethod-2.3 {"%%" is handled properly} {
    cleanup

    proc echo {args} { join $args "|" }

    type dog {
        delegate typemethod wag using {echo %%m %%%m}
    }

    dog wag
} {%m|%wag}

test dtypemethod-2.4 {Method "*" and "using"} {
    cleanup

    proc echo {args} { join $args "|" }

    type dog {
        delegate typemethod * using {echo %m}
    }

    list [dog wag] [dog bark loudly]
} {wag bark|loudly}


test dtypemethod-3.1 {typecomponent names can be changed dynamically} {
    cleanup

    proc echo {args} { join $args "|" }

    type dog {
        delegate typemethod length to mycomp

        typeconstructor {
            set mycomp string
        }

        typemethod switchit {} {
            set mycomp echo
        }
    }

    set a [dog length foo]
    dog switchit
    set b [dog length foo]
    
    list $a $b
} {3 length|foo}

test dtypemethod-4.1 {hierarchical typemethod, two tokens} {} {
    cleanup

    type tail {
        method wag {} {return "wags tail"}
    }

    type dog {
        typeconstructor {
            set tail [tail]
        }
        delegate typemethod {wag tail} to tail as wag
    }

    dog wag tail
} {wags tail}

test dtypemethod-4.2 {hierarchical typemethod, three tokens} {} {
    cleanup 

    type tail {
        method wag {} {return "wags tail"}
    }

    type dog {
        typeconstructor {
            set tail [tail]
        }
        delegate typemethod {wag tail proudly} to tail as wag
    }

    dog wag tail proudly
} {wags tail}

test dtypemethod-4.3 {hierarchical typemethod, three tokens} {} {
    cleanup

    type tail {
        method wag {} {return "wags tail"}
    }

    type dog {
        typeconstructor {
            set tail [tail]
        }
        delegate typemethod {wag tail really high} to tail as wag
    }

    dog wag tail really high
} {wags tail}

test dtypemethod-4.4 {redefinition is OK} {} {
    cleanup

    type tail {
        method {wag tail}    {} {return "wags tail"}
        method {wag briskly} {} {return "wags tail briskly"}
    }

    type dog {
        typeconstructor {
            set tail [tail]
        }
        delegate typemethod {wag tail} to tail as {wag tail}
        delegate typemethod {wag tail} to tail as {wag briskly}
    }

    dog wag tail
} {wags tail briskly}

test dtypemethod-4.5 {last token is used by default} {} {
    cleanup

    type tail {
        method wag {} {return "wags tail"}
    }

    type dog {
        typeconstructor {
            set tail [tail]
        }
        delegate typemethod {tail wag} to tail
    }

    dog tail wag
} {wags tail}

test dtypemethod-4.6 {last token can be *} {} {
    cleanup

    type tail {
        method wag {} {return "wags"}
        method droop {} {return "droops"}
    }

    type dog {
        typeconstructor {
            set tail [tail]
        }
        delegate typemethod {tail *} to tail
    }

    list [dog tail wag] [dog tail droop]
} {wags droops}

test dtypemethod-4.7 {except with multiple tokens} {} {
    cleanup

    type tail {
        method wag {} {return "wags"}
        method droop {} {return "droops"}
    }

    type dog {
        typeconstructor {
            set tail [tail]
        }
        delegate typemethod {tail *} to tail except droop
    }

    catch {dog tail droop} result

    list [dog tail wag] $result
} {wags {"::dog tail droop" is not defined}}

test dtypemethod-4.8 {"*" in the wrong spot} {} {
    cleanup

    catch {
        type dog {
            delegate typemethod {tail * wag} to tail
        }
    } result

    set result
} {Error in "delegate typemethod {tail * wag}...", "*" must be the last token.}

test dtypemethod-5.1 {prefix/typemethod collision} {} {
    cleanup

    catch {
        type dog {
            delegate typemethod wag to tail
            delegate typemethod {wag tail} to tail as wag
        }
    } result

    set result
} {Error in "delegate typemethod {wag tail}...", "wag" has no submethods.}

test dtypemethod-5.2 {prefix/typemethod collision} {} {
    cleanup

    catch {
        type dog {
            delegate typemethod {wag tail} to tail as wag
            delegate typemethod wag to tail
        }
    } result

    set result
} {Error in "delegate typemethod wag...", "wag" has submethods.}

test dtypemethod-5.3 {prefix/typemethod collision} {} {
    cleanup

    catch {
        type dog {
            delegate typemethod {wag tail} to tail
            delegate typemethod {wag tail proudly} to tail as wag
        }
    } result

    set result
} {Error in "delegate typemethod {wag tail proudly}...", "wag tail" has no submethods.}

test dtypemethod-5.4 {prefix/typemethod collision} {} {
    cleanup

    catch {
        type dog {
            delegate typemethod {wag tail proudly} to tail as wag
            delegate typemethod {wag tail} to tail
        }
    } result

    set result
} {Error in "delegate typemethod {wag tail}...", "wag tail" has submethods.}



#-----------------------------------------------------------------------
# type creation

test creation-1.1 {type instance names get qualified} {} {
    cleanup 

    type dog { }

    dog create spot
} {::spot}

test creation-1.2 {type instance names can be generated} {} {
    cleanup

    # Note: do not use type "abc" in any other test.
    type abc { }
    
    abc create my%AUTO%
} {::myabc1}

test creation-1.3 {"create" method is optional} {} {
    cleanup

    type dog { }

    dog fido
} {::fido}


test creation-1.4 {constructor arg can't be type} {} {
    cleanup

    catch {
        type dog { 
            constructor {type} { }
        }
    } result
    set result
} {constructor's arglist may not contain "type" explicitly}

test creation-1.5 {constructor arg can't be self} {} {
    cleanup

    catch {
        type dog { 
            constructor {self} { }
        }
    } result
    set result
} {constructor's arglist may not contain "self" explicitly}

test creation-1.6 {weird names are OK} {} {
    cleanup

    type confused-dog { 
        method meow {} {
            return "$self meows."
        }
    }

    confused-dog spot
    spot meow
} {::spot meows.}

test creation-1.7 {If -hasinstances yes, [$type] == [$type create %AUTO%]} {
    cleanup

    type dog {
        variable dummy
    }

    set mydog [dog]
} {::dog1}

test creation-1.8 {If -hasinstances no, [$type] != [$type create %AUTO%]} {
    cleanup

    type dog {
        pragma -hasinstances no
    }

    catch {set mydog [dog]} result
    set result
} {wrong # args: should be "::dog method args"}

test creation-1.9 {If widget, [$type] != [$type create %AUTO%]} tk {
    cleanup

    widget dog {
        variable dummy
    }

    catch {set mydog [dog]} result
    set result
} {wrong # args: should be "::dog method args"}


#-----------------------------------------------------------------------
# procs

test proc-1.1 {proc args can span multiple lines} {} {
    cleanup

    # This case caused an error at definition time in 0.9 because the
    # arguments were included in a comment in the compile script, and 
    # the subsequent lines weren't commented.
    type dog { 
        proc foo {
            a  
            b
        } { }
    }
} {::dog}


#-----------------------------------------------------------------------
# methods

test method-1.1 {methods get called} {} {
    cleanup

    type dog {
        method bark {} {
            return "$self barks"
        }
    }

    dog create spot
    spot bark
} {::spot barks}

test method-1.2 {methods can call other methods} {} {
    cleanup

    type dog {
        method bark {} {
            return "$self barks."
        }

        method chase {quarry} {
            return "$self chases $quarry; [$self bark]"
        }
    }

    dog create spot
    spot chase cat
} {::spot chases cat; ::spot barks.}

test method-1.3 {instances can call one another} {} {
    cleanup

    type dog {
        method bark {} {
            return "$self barks."
        }

        method chase {quarry} {
            return "$self chases $quarry; [$quarry bark] [$self bark]"
        }
    }

    dog create spot
    dog create fido
    spot chase ::fido
} {::spot chases ::fido; ::fido barks. ::spot barks.}

test method-1.4 {upvar works in methods} {} {
    cleanup

    type dog { 
        method goodname {varname} {
            upvar $varname myvar
            set myvar spot
        }
    }
    
    dog create fido
    set thename fido
    fido goodname thename
    set thename
} {spot}

test method-1.5 {unknown methods get an error} {} {
    cleanup

    type dog { }

    dog create spot
    set result ""
    catch {spot chase} result
    set result
} {"::spot chase" is not defined}

test method-1.6 {info type method returns the object's type} {} {
    cleanup

    type dog { }

    dog create spot
    spot info type
} {::dog}

test method-1.7 {instance method can call type method} {} {
    cleanup

    type dog { 
        typemethod hello {} {
            return "Hello"
        }
        method helloworld {} {
            return "[$type hello], World!"
        }
    }
    
    dog create spot
    spot helloworld
} {Hello, World!}

test method-1.8 {type methods must be qualified} {} {
    cleanup

    type dog { 
        typemethod hello {} {
            return "Hello"
        }
        method helloworld {} {
            return "[hello], World!"
        }
    }
    
    dog create spot
    catch {spot helloworld} result
    set result
} {invalid command name "hello"}

test method-1.9 {too few arguments} {} {
    cleanup

    type dog { 
        method bark {volume} { }
    }

    dog create spot
    set result ""
    catch {spot bark} result
    set result
} {wrong # args: should be "::dog::Snit_methodbark type selfns win self volume"}

test method-1.10 {too many arguments} {} {
    cleanup

    type dog { 
        method bark {volume} { }
    }

    dog create spot
    set result ""
    catch {spot bark really loud} result
    set result
} {wrong # args: should be "::dog::Snit_methodbark type selfns win self volume"}

test method-1.11 {method args can't include type} {} {
    cleanup

    catch {
        type dog { 
            method foo {a type b} { }
        }
    } result
    set result
} {method foo's arglist may not contain "type" explicitly}

test method-1.12 {method args can't include self} {} {
    cleanup

    catch {
        type dog { 
            method foo {a self b} { }
        }
    } result
    set result
} {method foo's arglist may not contain "self" explicitly}

test method-1.13 {method args can span multiple lines} {} {
    cleanup

    # This case caused an error at definition time in 0.9 because the
    # arguments were included in a comment in the compile script, and 
    # the subsequent lines weren't commented.
    type dog { 
        method foo {
                    a  
                    b
                } { }
    }
} {::dog}

#-----------------------------------------------------------------------
# hierarchical methods

test hmethod-1.1 {hierarchical method, two tokens} {} {
    cleanup

    type dog {
        method {wag tail} {} {
            return "$self wags tail."
        }
    }

    dog spot
    spot wag tail
} {::spot wags tail.}

test hmethod-1.2 {hierarchical method, three tokens} {} {
    cleanup

    type dog {
        method {wag tail proudly} {} {
            return "$self wags tail proudly."
        }
    }

    dog spot
    spot wag tail proudly
} {::spot wags tail proudly.}

test hmethod-1.3 {hierarchical method, three tokens} {} {
    cleanup

    type dog {
        method {wag tail really high} {} {
            return "$self wags tail really high."
        }
    }

    dog spot
    spot wag tail really high
} {::spot wags tail really high.}

test hmethod-1.4 {redefinition is OK} {} {
    cleanup

    type dog {
        method {wag tail} {} {
            return "$self wags tail."
        }
        method {wag tail} {} {
            return "$self wags tail briskly."
        }
    }

    dog spot
    spot wag tail
} {::spot wags tail briskly.}

test hmethod-1.5 {proper error on missing submethod} {} {
    cleanup

    type dog {
        method {wag tail} {} { }
    }

    dog spot
    catch {spot wag} result
    set result
} {wrong number args: should be "::spot wag method args"}


test hmethod-2.1 {prefix/method collision} {} {
    cleanup

    catch {
        type dog {
            method wag {} {}
            method {wag tail} {} {
                return "$self wags tail."
            }
        }
    } result

    set result
} {Error in "method {wag tail}...", "wag" has no submethods.}

test hmethod-2.2 {prefix/method collision} {} {
    cleanup

    catch {
        type dog {
            method {wag tail} {} {
                return "$self wags tail."
            }
            method wag {} {}
        }
    } result

    set result
} {Error in "method wag...", "wag" has submethods.}

test hmethod-2.3 {prefix/method collision} {} {
    cleanup

    catch {
        type dog {
            method {wag tail} {} {}
            method {wag tail proudly} {} {
                return "$self wags tail."
            }
        }
    } result

    set result
} {Error in "method {wag tail proudly}...", "wag tail" has no submethods.}

test hmethod-2.4 {prefix/method collision} {} {
    cleanup

    catch {
        type dog {
            method {wag tail proudly} {} {
                return "$self wags tail."
            }
            method {wag tail} {} {}
        }
    } result

    set result
} {Error in "method {wag tail}...", "wag tail" has submethods.}

#-----------------------------------------------------------------------
# mymethod and renaming

test rename-1.1 {mymethod uses name of instance name variable} {} {
    cleanup

    type dog { 
        method mymethod {} {
            list [mymethod] [mymethod "A B"] [mymethod A B]
        }
    }

    dog fido
    fido mymethod
} {{::snit::RT.CallInstance ::dog::Snit_inst1} {::snit::RT.CallInstance ::dog::Snit_inst1 {A B}} {::snit::RT.CallInstance ::dog::Snit_inst1 A B}}

test rename-1.2 {instances can be renamed} {} {
    cleanup

    type dog { 
        method names {} {
            list [mymethod] $selfns $win $self
        }
    }

    dog fido
    set a [fido names]
    rename fido spot
    set b [spot names]

    concat $a $b
} {{::snit::RT.CallInstance ::dog::Snit_inst1} ::dog::Snit_inst1 ::fido ::fido {::snit::RT.CallInstance ::dog::Snit_inst1} ::dog::Snit_inst1 ::fido ::spot}

test rename-1.3 {rename to "" deletes an instance} {} {
    cleanup

    type dog { }

    dog fido
    rename fido ""
    namespace children ::dog
} {}

test rename-1.4 {rename to "" deletes an instance even after a rename} {} {
    cleanup

    type dog { }

    dog fido
    rename fido spot
    rename spot ""
    namespace children ::dog
} {}

test rename-1.5 {creating an object twice destroys the first instance} {} {
    cleanup

    type dog { 
        # Can't even test this normally.
        pragma -canreplace yes
    }

    dog fido
    set a [namespace children ::dog]
    dog fido
    set b [namespace children ::dog]
    fido destroy
    set c [namespace children ::dog]

    list $a $b $c
} {::dog::Snit_inst1 ::dog::Snit_inst2 {}}

#-----------------------------------------------------------------------
# mymethod actually works

test mymethod-1.1 {run mymethod handler} {
    cleanup

    type foo {
	option -command {}
	method runcmd {} {
	    eval [linsert $options(-command) end $self snarf]
	    return
	}
    }
    type bar {
	variable sub
	constructor {args} {
	    set sub [foo fubar -command [mymethod Handler]]
	    return
	}

	method Handler {args} {
	    set ::RES $args
	}

	method test {} {
	    $sub runcmd
	    return
	}
    }

    set ::RES {}
    bar boogle
    boogle test
    set ::RES
} {::bar::fubar snarf}

#-----------------------------------------------------------------------
# myproc 

test myproc-1.1 {myproc qualifies proc names} {} {
    cleanup

    type dog {
        proc foo {} {}

        typemethod getit {} {
            return [myproc foo]
        }
    }

    dog getit
} {::dog::foo}

test myproc-1.2 {myproc adds arguments} {} {
    cleanup

    type dog {
        proc foo {} {}

        typemethod getit {} {
            return [myproc foo "a b"]
        }
    }

    dog getit
} {::dog::foo {a b}}

test myproc-1.3 {myproc adds arguments} {} {
    cleanup

    type dog {
        proc foo {} {}

        typemethod getit {} {
            return [myproc foo "a b" c d]
        }
    }

    dog getit
} {::dog::foo {a b} c d}

test myproc-1.4 {procs with selfns work} {} {
    cleanup

    type dog {
        variable datum foo

        method qualify {} {
            return [myproc getdatum $selfns]
        }
        proc getdatum {selfns} {
            return $datum
        }
    }
    dog create spot
    eval [spot qualify]
} {foo}


#-----------------------------------------------------------------------
# mytypemethod

test mytypemethod-1.1 {mytypemethod qualifies typemethods} {} {
    cleanup

    type dog {
        typemethod this {} {}

        typemethod a {} {
            return [mytypemethod this]
        }
        typemethod b {} {
            return [mytypemethod this x]
        }
        typemethod c {} {
            return [mytypemethod this "x y"]
        }
        typemethod d {} {
            return [mytypemethod this x y]
        }
    }
    
    list [dog a] [dog b] [dog c] [dog d]
} {{::dog this} {::dog this x} {::dog this {x y}} {::dog this x y}}

#-----------------------------------------------------------------------
# typevariable

test typevariable-1.1 {typevarname qualifies typevariables} {} {
    # Note: typevarname is DEPRECATED.  Use mytypevar instead.
    cleanup

    type dog { 
        method tvname {name} {
            typevarname $name
        }
    }

    dog create spot
    spot tvname myvar
} {::dog::myvar}

test typevariable-1.2 {undefined typevariables are OK} {} {
    cleanup

    type dog { 
        method tset {value} {
            typevariable theValue

            set theValue $value
        }

        method tget {} {
            typevariable theValue

            return $theValue
        }
    }

    dog create spot
    dog create fido
    spot tset Howdy

    list [spot tget] [fido tget] [set ::dog::theValue]
} {Howdy Howdy Howdy}

test typevariable-1.3 {predefined typevariables are OK} {} {
    cleanup

    type dog { 
        typevariable greeting Hello

        method tget {} {
            return $greeting
        }
    }

    dog create spot
    dog create fido

    list [spot tget] [fido tget] [set ::dog::greeting]
} {Hello Hello Hello}

test typevariable-1.4 {typevariables can be arrays} {} {
    cleanup

    type dog { 
        typevariable greetings

        method fill {} {
            set greetings(a) Hi
            set greetings(b) Howdy
        }
    }

    dog create spot
    spot fill
    list $::dog::greetings(a) $::dog::greetings(b)
} {Hi Howdy}

test typevariable-1.5 {typevariables can used in typemethods} {} {
    cleanup

    type dog { 
        typevariable greetings Howdy

        typemethod greet {} {
            return $greetings
        }
    }

    dog greet
} {Howdy}

test typevariable-1.6 {typevariables can used in procs} {} {
    cleanup

    type dog { 
        typevariable greetings Howdy

        method greet {} {
            return [realGreet]
        }

        proc realGreet {} {
            return $greetings
        }
    }

    dog create spot
    spot greet
} {Howdy}

test typevariable-1.7 {mytypevar qualifies typevariables} {} {
    cleanup

    type dog { 
        method tvname {name} {
            mytypevar $name
        }
    }

    dog create spot
    spot tvname myvar
} {::dog::myvar}

test typevariable-1.8 {typevariable with too many initializers throws an error} {} {
    cleanup

    catch {
        type dog { 
            typevariable color dark brown
        }
    } result

    set result
} {Error in "typevariable color...", too many initializers}

test typevariable-1.9 {typevariable with too many initializers throws an error} {} {
    cleanup

    catch {
        type dog { 
            typevariable color -array dark brown
        }
    } result

    set result
} {Error in "typevariable color...", too many initializers}

test typevariable-1.10 {typevariable can initialize array variables} {} {
    cleanup

    type dog { 
        typevariable data -array {
            family jones
            color brown
        }

        typemethod getdata {item} {
            return $data($item)
        }
    }

    list [dog getdata family] [dog getdata color]
} {jones brown}

#-----------------------------------------------------------------------
# instance variable

test ivariable-1.1 {myvar qualifies instance variables} {} {
    cleanup

    type dog { 
        method vname {name} {
            myvar $name
        }
    }

    dog create spot
    spot vname somevar
} {::dog::Snit_inst1::somevar}

test ivariable-1.2 {undefined instance variables are OK} {} {
    cleanup

    type dog { 
        method setgreeting {value} {
            variable greeting

            set greeting $value
        }

        method getgreeting {} {
            variable greeting

            return $greeting
        }
    }

    set spot [dog create spot]
    spot setgreeting Hey

    dog create fido
    fido setgreeting Howdy

    list [spot getgreeting] [fido getgreeting] [set ::dog::Snit_inst1::greeting]
} {Hey Howdy Hey}

test ivariable-1.3 {instance variables are destroyed automatically} {} {
    cleanup

    type dog { 
        constructor {args} {
            variable greeting

            set greeting Hi
        }
    }

    dog create spot
    set g1 $::dog::Snit_inst1::greeting

    spot destroy
    list $g1 [info exists ::dog::Snit_inst1::greeting]
} {Hi 0}

test ivariable-1.4 {defined instance variables need not be declared} {} {
    cleanup

    type dog { 
        variable greetings

        method put {} {
            set greetings Howdy
        }

        method get {} {
            return $greetings
        }
    }

    dog create spot
    spot put
    spot get
} {Howdy}

test ivariable-1.5 {instance variables can be arrays} {} {
    cleanup

    type dog { 
        variable greetings

        method fill {} {
            set greetings(a) Hi
            set greetings(b) Howdy
        }

        method vname {} {
            return [myvar greetings]
        }
    }

    dog create spot
    spot fill
    list [set [spot vname](a)] [set [spot vname](b)]
} {Hi Howdy}

test ivariable-1.6 {instance variables can be initialized in the definition} {} {
    cleanup

    type dog { 
        variable greetings {Hi Howdy}
        variable empty {}

        method list {} {
            list $greetings $empty
        }
    }

    dog create spot
    spot list
} {{Hi Howdy} {}}

test ivariable-1.7 {variable is illegal when selfns is undefined} {} {
    cleanup

    type dog { 
        method caller {} {
            callee
        }
        proc callee {} {
            variable foo
        }
    }

    dog create spot

    set result ""
    catch {spot caller} result
    set result
} {can't read "selfns": no such variable}

test ivariable-1.8 {myvar is illegal when selfns is undefined} {} {
    cleanup

    type dog { 
        method caller {} {
            callee
        }
        proc callee {} {
            myvar foo
        }
    }

    dog create spot

    set result ""
    catch {spot caller} result
    set result
} {can't read "selfns": no such variable}

test ivariable-1.9 {procs which define selfns see instance variables} {} {
    cleanup

    type dog { 
        variable greeting Howdy

        method caller {} {
            return [callee $selfns]
        }

        proc callee {selfns} {
            return $greeting
        }
    }

    dog create spot

    spot caller
} {Howdy}

test ivariable-1.10 {in methods, variable works with fully qualified names} {} {
    cleanup

    namespace eval ::somenamespace:: {
        set somevar somevalue
    }

    type dog { 
        method get {} {
            variable ::somenamespace::somevar
            return $somevar
        }
    }

    dog create spot

    spot get
} {somevalue}

test ivariable-1.11 {variable with too many initializers throws an error} {} {
    cleanup

    catch {
        type dog { 
            variable color dark brown
        }
    } result

    set result
} {Error in "variable color...", too many initializers}

test ivariable-1.12 {variable with too many initializers throws an error} {} {
    cleanup

    catch {
        type dog { 
            variable color -array dark brown
        }
    } result

    set result
} {Error in "variable color...", too many initializers}

test ivariable-1.13 {variable can initialize array variables} {} {
    cleanup

    type dog { 
        variable data -array {
            family jones
            color brown
        }

        method getdata {item} {
            return $data($item)
        }
    }

    dog spot
    list [spot getdata family] [spot getdata color]
} {jones brown}

#-----------------------------------------------------------------------
# codename
#
# NOTE: codename is deprecated; myproc should be used instead.

test codename-1.1 {codename qualifies procs} {} {
    cleanup

    type dog {
        method qualify {} {
            return [codename myproc]
        }
        proc myproc {} { }
    }
    dog create spot
    spot qualify
} {::dog::myproc}

test codename-1.2 {procs with selfns work} {} {
    cleanup

    type dog {
        variable datum foo

        method qualify {} {
            return [list [codename getdatum] $selfns]
        }
        proc getdatum {selfns} {
            return $datum
        }
    }
    dog create spot
    eval [spot qualify]
} {foo}

#-----------------------------------------------------------------------
# Options

test option-1.1 {options get default values} {} {
    cleanup

    type dog {
        option -color golden
    }

    dog create spot
    spot cget -color
} {golden}

test option-1.2 {options can be set} {} {
    cleanup

    type dog {
        option -color golden
    }

    dog create spot
    spot configure -color black
    spot cget -color
} {black}

test option-1.3 {multiple options can be set} {} {
    cleanup

    type dog {
        option -color golden
        option -akc 0
    }

    dog create spot
    spot configure -color brown -akc 1
    list [spot cget -color] [spot cget -akc]
} {brown 1}

test option-1.4 {options can be retrieved as instance variable} {} {
    cleanup

    type dog {
        option -color golden
        option -akc 0

        method listopts {} {
            list $options(-color) $options(-akc)
        }
    }

    dog create spot
    spot configure -color black -akc 1
    spot listopts
} {black 1}

test option-1.5 {options can be set as an instance variable} {} {
    cleanup

    type dog {
        option -color golden
        option -akc 0

        method setopts {} {
            set options(-color) black
            set options(-akc) 1
        }
    }

    dog create spot
    spot setopts
    list [spot cget -color] [spot cget -akc]
} {black 1}

test option-1.6 {options can be set at creation time} {} {
    cleanup

    type dog {
        option -color golden
        option -akc 0
    }

    dog create spot -color white -akc 1
    list [spot cget -color] [spot cget -akc]
} {white 1}

test option-1.7 {undefined option: cget} {} {
    cleanup

    type dog {
        option -color golden
        option -akc 0
    }

    dog create spot
    set result {}
    catch {spot cget -colour} result
    set result
} {unknown option "-colour"}

test option-1.8 {undefined option: configure} {} {
    cleanup

    type dog {
        option -color golden
        option -akc 0
    }

    dog create spot
    set result {}
    catch {spot configure -colour blue} result
    set result
} {unknown option "-colour"}

test option-1.9 {options default to ""} {} {
    cleanup

    type dog {
        option -color
    }

    dog create spot
    spot cget -color
} {}

test option-1.10 {spaces allowed in option defaults} {} {
    cleanup

    type dog {
        option -breed "golden retriever"
    }
    dog fido
    fido cget -breed
} {golden retriever}

test option-1.11 {brackets allowed in option defaults} {} {
    cleanup

    type dog {
        option -regexp {[a-z]+}
    }

    dog fido
    fido cget -regexp
} {[a-z]+}

test option-2.1 {configure returns info, local options only} {} {
    cleanup

    type dog {
        option -color black
        option -akc 1
    }

    dog create spot
    spot configure -color red
    spot configure -akc 0
    spot configure
} {{-color color Color black red} {-akc akc Akc 1 0}}

test option-2.2 {configure -opt returns info, local options only} {} {
    cleanup

    type dog {
        option -color black
        option -akc 1
    }

    dog create spot
    spot configure -color red
    spot configure -color
} {-color color Color black red}

test option-2.3 {configure -opt returns info, explicit options} {} {
    cleanup

    type papers {
        option -akcflag 1
    }

    type dog {
        option -color black
        delegate option -akc to papers as -akcflag
        constructor {args} {
            set papers [papers create $self.papers]
        }

        destructor {
            catch {$self.papers destroy}
        }
    }

    dog create spot
    spot configure -akc 0
    spot configure -akc
} {-akc akc Akc 1 0}

test option-2.4 {configure -unknownopt} {} {
    cleanup

    type papers {
        option -akcflag 1
    }

    type dog {
        option -color black
        delegate option -akc to papers as -akcflag
        constructor {args} {
            set papers [papers create $self.papers]
        }

        destructor {
            catch {$self.papers destroy}
        }
    }


    dog create spot
    catch {spot configure -foo} result
    set result
} {unknown option "-foo"}

test option-2.5 {configure returns info, unknown options} tk {
    cleanup

    widgetadaptor myframe { 
        option -foo a
        delegate option -width to hull
        delegate option * to hull
        constructor {args} {
            installhull [frame $self]
        }
    }

    myframe .frm
    set a [.frm configure -foo]
    set b [.frm configure -width]
    set c [.frm configure -height]
    destroy .frm
    tkbide

    list $a $b $c

} {{-foo foo Foo a a} {-width width Width 0 0} {-height height Height 0 0}}

test option-2.6 {configure -opt unknown to implicit component} tk {
    cleanup

    widgetadaptor myframe { 
        delegate option * to hull
        constructor {args} {
            installhull [frame $self]
        }
    }
    myframe .frm
    catch {.frm configure -quux} result
    destroy .frm
    tkbide
    set result
} {unknown option "-quux"}

test option-3.1 {set option resource name explicitly} {
    cleanup

    type dog {
        option {-tailcolor tailColor} black
    }

    dog fido

    fido configure -tailcolor
} {-tailcolor tailColor TailColor black black}

test option-3.2 {set option class name explicitly} {
    cleanup

    type dog {
        option {-tailcolor tailcolor TailColor} black
    }

    dog fido

    fido configure -tailcolor
} {-tailcolor tailcolor TailColor black black}

test option-3.3 {delegated option's names come from owner} {
    cleanup
    
    type tail {
        option -color black
    }

    type dog {
        delegate option -tailcolor to tail as -color

        constructor {args} {
            set tail [tail fidotail]
        }
    }

    dog fido

    fido configure -tailcolor
} {-tailcolor tailcolor Tailcolor black black}

test option-3.4 {delegated option's resource name set explicitly} {
    cleanup
    
    type tail {
        option -color black
    }

    type dog {
        delegate option {-tailcolor tailColor} to tail as -color

        constructor {args} {
            set tail [tail fidotail]
        }
    }

    dog fido

    fido configure -tailcolor
} {-tailcolor tailColor TailColor black black}

test option-3.5 {delegated option's class name set explicitly} {
    cleanup
    
    type tail {
        option -color black
    }

    type dog {
        delegate option {-tailcolor tailcolor TailColor} to tail as -color

        constructor {args} {
            set tail [tail fidotail]
        }
    }

    dog fido

    fido configure -tailcolor
} {-tailcolor tailcolor TailColor black black}

test option-3.6 {delegated option's default comes from component} {
    cleanup
    
    type tail {
        option -color black
    }

    type dog {
        delegate option -tailcolor to tail as -color

        constructor {args} {
            set tail [tail fidotail -color red]
        }
    }

    dog fido

    fido configure -tailcolor
} {-tailcolor tailcolor Tailcolor black red}

test option-4.1 {local option name must begin with hyphen} {
    cleanup

    catch {
        type dog {
            option nohyphen
        }
    } result

    set result
} {Error in "option nohyphen...", badly named option "nohyphen"}

test option-4.2 {local option name must be lower case} {
    cleanup

    catch {
        type dog {
            option -Upper
        }
    } result

    set result
} {Error in "option -Upper...", badly named option "-Upper"}

test option-4.3 {local option name may not contain spaces} {
    cleanup

    catch {
        type dog {
            option {"-with space"}
        }
    } result

    set result
} {Error in "option {"-with space"}...", badly named option "-with space"}

test option-4.4 {delegated option name must begin with hyphen} {
    cleanup

    catch {
        type dog {
            delegate option nohyphen to tail
        }
    } result

    set result
} {Error in "delegate option nohyphen...", badly named option "nohyphen"}

test option-4.5 {delegated option name must be lower case} {
    cleanup

    catch {
        type dog {
            delegate option -Upper to tail
        }
    } result

    set result
} {Error in "delegate option -Upper...", badly named option "-Upper"}

test option-4.6 {delegated option name may not contain spaces} {
    cleanup

    catch {
        type dog {
            delegate option {"-with space"} to tail
        }
    } result

    set result
} {Error in "delegate option {"-with space"}...", badly named option "-with space"}

test option-5.1 {local widget options read from option database} tk {
    cleanup

    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

} {a bb}

test option-5.2 {local option database values available in constructor} tk {
    cleanup

    widget dog { 
        option -bar b
        variable saveit

        typeconstructor {
            option add *Dog.bar bb
        }

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

        method getit {} {
            return $saveit
        }
    }

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

    set result
} {bb}

test option-6.1 {if no options, no options variable} {
    cleanup
    
    type dog {
        variable dummy
    }

    dog spot
    spot info vars options
} {}

test option-6.2 {if no options, no options methods} {
    cleanup
    
    type dog {
        variable dummy
    }

    dog spot
    spot info methods c*
} {}

#-----------------------------------------------------------------------
# onconfigure

test onconfigure-1.1 {invalid onconfigure methods are caught} {} {
    cleanup

    catch {
        type dog { 
            onconfigure -color {value} { }
        }
    } result
    set result
} {onconfigure -color: option "-color" unknown}

test onconfigure-1.2 {onconfigure methods take one argument} {} {
    cleanup

    catch {
        type dog { 
            option -color golden

            onconfigure -color {value badarg} { }
        }
    } result
    set result
} {onconfigure -color handler should have one argument, got "value badarg"}

test onconfigure-1.3 {onconfigure methods work} {} {
    cleanup

    type dog { 
        option -color golden
        
        onconfigure -color {value} { 
            set options(-color) "*$value*"
        }
    }

    dog create spot
    spot configure -color brown
    spot cget -color
} {*brown*}

test onconfigure-1.4 {onconfigure arg can't be type} {} {
    cleanup

    catch {
        type dog { 
            option -color
            onconfigure -color {type} { }
        }
    } result
    set result
} {onconfigure -color's arglist may not contain "type" explicitly}

test onconfigure-1.5 {onconfigure arg can't be self} {} {
    cleanup

    catch {
        type dog { 
            option -color
            onconfigure -color {self} { }
        }
    } result
    set result
} {onconfigure -color's arglist may not contain "self" explicitly}

#-----------------------------------------------------------------------
# oncget

test oncget-1.1 {invalid oncget methods are caught} {} {
    cleanup

    catch {
        type dog {
            oncget -color { }
        }
    } result
    set result
} {Error in "oncget -color...", option "-color" unknown}

test oncget-1.2 {oncget methods work} {} {
    cleanup

    type dog { 
        option -color golden
        
        oncget -color { 
            return "*$options(-color)*"
        }
    }

    dog create spot
    spot configure -color brown
    spot cget -color
} {*brown*}

#-----------------------------------------------------------------------
# constructor


test constructor-1.1 {constructor can do things} {} {
    cleanup

    type dog {
        variable a
        variable b
        constructor {args} {
            set a 1
            set b 2
        }
        method foo {} {
            list $a $b
        }
    }

    dog create spot
    spot foo
} {1 2}

test constructor-1.2 {constructor with no configurelist ignores args} {} {
    cleanup

    type dog {
        constructor {args} { }
        option -color golden
        option -akc 0
    }

    dog create spot -color white -akc 1
    list [spot cget -color] [spot cget -akc]
} {golden 0}

test constructor-1.3 {constructor with configurelist gets args} {} {
    cleanup

    type dog {
        constructor {args} {
            $self configurelist $args
        }
        option -color golden
        option -akc 0
    }

    dog create spot -color white -akc 1
    list [spot cget -color] [spot cget -akc]
} {white 1}

test constructor-1.4 {constructor with specific args} {} {
    cleanup

    type dog {
        option -value ""
        constructor {a b args} {
            set options(-value) [list $a $b $args]
        }
    }

    dog spot retriever golden -akc 1
    spot cget -value
} {retriever golden {-akc 1}}

test constructor-1.5 {constructor with list as one list arg} {} {
    cleanup

    type dog {
        option -value ""
        constructor {args} {
            set options(-value) $args
        }
    }

    dog spot {retriever golden}
    spot cget -value
} {{retriever golden}}

test constructor-1.6 {default constructor configures options} {} {
    cleanup

    type dog {
        option -color brown
        option -breed mutt
    }

    dog spot -color golden -breed retriever
    list [spot cget -color] [spot cget -breed]
} {golden retriever}

test constructor-1.7 {default constructor takes no args if no options} {} {
    cleanup

    type dog {
        variable color
    }

    catch {dog spot -color golden} result
    set result
} {Error in constructor: wrong # args: should be "::dog::Snit_constructor type selfns win self"}


#-----------------------------------------------------------------------
# destroy

test destroy-1.1 {destroy cleans up the instance} {} {
    cleanup

    type dog { 
        option -color golden
    }
    
    set a [namespace children ::dog::]
    dog create spot
    set b [namespace children ::dog::]
    spot destroy
    set c [namespace children ::dog::]
    list $a $b $c [info commands ::dog::spot]
} {{} ::dog::Snit_inst1 {} {}}

test destroy-1.2 {incomplete objects are destroyed} {} {
    cleanup

    array unset ::dog::snit_ivars

    type dog { 
        option -color golden
        
        constructor {args} {
            $self configurelist $args

            if {"red" == [$self cget -color]} {
                error "No Red Dogs!"
            }
        }
    }
    
    catch {dog create spot -color red} result
    set names [array names ::dog::snit_ivars]
    list $result $names [info commands ::dog::spot]
} {{Error in constructor: No Red Dogs!} {} {}}

test destroy-1.3 {user-defined destructors are called} {} {
    cleanup

    type dog { 
        typevariable flag ""
        
        constructor {args} {
            set flag "created $self"
        }

        destructor {
            set flag "destroyed $self"
        }

        typemethod getflag {} {
            return $flag
        }
    }
    
    dog create spot
    set a [dog getflag]
    spot destroy
    list $a [dog getflag]
} {{created ::spot} {destroyed ::spot}}


#-----------------------------------------------------------------------
# delegate: general syntax tests

test delegate-1.1 {can only delegate methods or options} {} {
    cleanup

    set result ""
    catch {
        type dog {
            delegate foo bar to baz
        }
    } result

    set result
} {Error in "delegate foo bar...", "foo"?}

test delegate-1.2 {"to" must appear in the right place} {} {
    cleanup

    set result ""
    catch {
        type dog {
            delegate method foo from bar
        }
    } result

    set result
} {Error in "delegate method foo...", unknown delegation option "from"}

test delegate-1.3 {"as" must have a target} {} {
    cleanup

    set result ""
    catch {
        type dog {
            delegate method foo to bar as
        }
    } result

    set result
} {Error in "delegate method foo...", invalid syntax}

test delegate-1.4 {"as" must have a single target} {} {
    cleanup

    set result ""
    catch {
        type dog {
            delegate method foo to bar as baz quux
        }
    } result

    set result
} {Error in "delegate method foo...", unknown delegation option "quux"}

test delegate-1.5 {"as" doesn't work with "*"} {} {
    cleanup

    set result ""
    catch {
        type dog {
            delegate method * to hull as foo
        }
    } result

    set result
} {Error in "delegate method *...", cannot specify "as" with "*"}

test delegate-1.6 {"except" must have a target} {} {
    cleanup

    set result ""
    catch {
        type dog {
            delegate method * to bar except
        }
    } result

    set result
} {Error in "delegate method *...", invalid syntax}

test delegate-1.7 {"except" must have a single target} {} {
    cleanup

    set result ""
    catch {
        type dog {
            delegate method * to bar except baz quux
        }
    } result

    set result
} {Error in "delegate method *...", unknown delegation option "quux"}

test delegate-1.8 {"except" works only with "*"} {} {
    cleanup

    set result ""
    catch {
        type dog {
            delegate method foo to hull except bar
        }
    } result

    set result
} {Error in "delegate method foo...", can only specify "except" with "*"}

test delegate-1.9 {only "as" or "except"} {} {
    cleanup

    set result ""
    catch {
        type dog {
            delegate method foo to bar with quux
        }
    } result

    set result
} {Error in "delegate method foo...", unknown delegation option "with"}


#-----------------------------------------------------------------------
# delegated methods

test dmethod-1.1 {delegate method to non-existent component} {} {
    cleanup

    set result ""

    type dog {
        delegate method foo to bar
    }

    dog create spot
    catch {spot foo} result
    set result
} {::dog ::spot delegates method "foo" to undefined component "bar"}

test dmethod-1.2 {delegating to existing component} {
    cleanup

    type dog {
        constructor {args} {
            set string string
        }

        delegate method length to string
    }

    dog create spot
    spot length foo
} {3}

test dmethod-1.3 {delegating to existing component with error} {
    cleanup

    type dog {
        constructor {args} {
            set string string
        }

        delegate method length to string
    }

    dog create spot
    set result ""
    catch {spot length foo bar} result
    set result
} {wrong # args: should be "string length string"}

test dmethod-1.4 {delegating unknown methods to existing component} {
    cleanup

    type dog {
        constructor {args} {
            set string string
        }

        delegate method * to string
    }

    dog create spot
    spot length foo
} {3}

test dmethod-1.5 {delegating unknown method to existing component with error} {
    cleanup

    type dog {
        constructor {args} {
            set stringhandler string
        }

        delegate method * to stringhandler
    }

    dog create spot
    set result ""
    catch {spot foo bar} result
    set result
} {bad option "foo": must be bytelength, compare, equal, first, index, is, last, length, map, match, range, repeat, replace, tolower, toupper, totitle, trim, trimleft, trimright, wordend, or wordstart}

test dmethod-1.6 {can't delegate local method: order 1} {
    cleanup

    catch {
        type cat {
            method foo {} {}
            delegate method foo to hull
        }
    } result

    set result
} {Error in "delegate method foo...", "foo" has been defined locally.}

test dmethod-1.7 {can't delegate local method: order 2} {
    cleanup

    catch {
        type cat {
            delegate method foo to hull
            method foo {} {}
        }
    } result

    set result
} {Error in "method foo...", "foo" has been delegated}

test dmethod-1.8 {excepted methods are caught properly} {
    cleanup

    type tail {
        method wag {}    {return "wagged"}
        method flaunt {} {return "flaunted"}
        method tuck {}   {return "tuck"}
    }

    type cat {
        method meow {} {}
        delegate method * to tail except {wag tuck}

        constructor {args} {
            set tail [tail %AUTO%]
        }
    }

    cat fifi

    catch {fifi flaunt} a
    catch {fifi wag}    b
    catch {fifi tuck}   c
    
    list $a $b $c
} {flaunted {"::fifi wag" is not defined} {"::fifi tuck" is not defined}}

test dmethod-1.9 {as clause can include arguments} {
    cleanup

    type tail {
        method wag {adverb}    {return "wagged $adverb"}
    }

    type dog {
        delegate method wag to tail as {wag briskly}

        constructor {args} {
            set tail [tail %AUTO%]
        }
    }

    dog spot

    spot wag
} {wagged briskly}

test dmethod-2.1 {'using "%c %m"' gets normal behavior} {
    cleanup

    type tail {
        method wag {adverb}    {return "wagged $adverb"}
    }

    type dog {
        delegate method wag to tail using {%c %m}

        constructor {args} {
            set tail [tail %AUTO%]
        }
    }

    dog spot

    spot wag briskly
} {wagged briskly}

test dmethod-2.2 {All 'using' conversions are converted} {
    cleanup

    proc echo {args} { return $args }

    type dog {
        delegate method {tail wag} using {echo %% %t %M %m %j %n %w %s %c}
    }

    dog spot

    spot tail wag
} {% ::dog {tail wag} wag tail_wag ::dog::Snit_inst1 ::spot ::spot %c}

test dmethod-2.3 {"%%" is handled properly} {
    cleanup

    proc echo {args} { join $args "|" }

    type dog {
        delegate method wag using {echo %%m %%%m}
    }

    dog spot

    spot wag
} {%m|%wag}

test dmethod-2.4 {Method "*" and "using"} {
    cleanup

    proc echo {args} { join $args "|" }

    type dog {
        delegate method * using {echo %m}
    }

    dog spot

    list [spot wag] [spot bark loudly]
} {wag bark|loudly}


test dmethod-3.1 {component names can be changed dynamically} {
    cleanup

    type tail1 {
        method wag {}    {return "wagged"}
    }

    type tail2 {
        method wag {}    {return "drooped"}
    }

    type dog {
        delegate method wag to tail

        constructor {args} {
            set tail [tail1 %AUTO%]
        }

        method switchit {} {
            set tail [tail2 %AUTO%]
        }
    }

    dog fido

    set a [fido wag]
    fido switchit
    set b [fido wag]
    
    list $a $b
} {wagged drooped}

test dmethod-4.1 {hierarchical method, two tokens} {} {
    cleanup

    type tail {
        method wag {} {return "wags tail"}
    }

    type dog {
        constructor {} {
            set tail [tail]
        }
        delegate method {wag tail} to tail as wag
    }

    dog spot
    spot wag tail
} {wags tail}

test dmethod-4.2 {hierarchical method, three tokens} {} {
    cleanup 

    type tail {
        method wag {} {return "wags tail"}
    }

    type dog {
        constructor {} {
            set tail [tail]
        }
        delegate method {wag tail proudly} to tail as wag
    }

    dog spot
    spot wag tail proudly
} {wags tail}

test dmethod-4.3 {hierarchical method, three tokens} {} {
    cleanup

    type tail {
        method wag {} {return "wags tail"}
    }

    type dog {
        constructor {} {
            set tail [tail]
        }
        delegate method {wag tail really high} to tail as wag
    }

    dog spot
    spot wag tail really high
} {wags tail}

test dmethod-4.4 {redefinition is OK} {} {
    cleanup

    type tail {
        method {wag tail}    {} {return "wags tail"}
        method {wag briskly} {} {return "wags tail briskly"}
    }

    type dog {
        constructor {} {
            set tail [tail]
        }
        delegate method {wag tail} to tail as {wag tail}
        delegate method {wag tail} to tail as {wag briskly}
    }

    dog spot
    spot wag tail
} {wags tail briskly}

test dmethod-4.5 {all tokens are used by default} {} {
    cleanup

    type tail {
        method wag {} {return "wags tail"}
    }

    type dog {
        constructor {} {
            set tail [tail]
        }
        delegate method {tail wag} to tail
    }

    dog spot
    spot tail wag
} {wags tail}

test dmethod-4.6 {last token can be *} {} {
    cleanup

    type tail {
        method wag {} {return "wags"}
        method droop {} {return "droops"}
    }

    type dog {
        constructor {} {
            set tail [tail]
        }
        delegate method {tail *} to tail
    }

    dog spot

    list [spot tail wag] [spot tail droop]
} {wags droops}

test dmethod-4.7 {except with multiple tokens} {} {
    cleanup

    type tail {
        method wag {} {return "wags"}
        method droop {} {return "droops"}
    }

    type dog {
        constructor {} {
            set tail [tail]
        }
        delegate method {tail *} to tail except droop
    }

    dog spot

    catch {spot tail droop} result

    list [spot tail wag] $result
} {wags {"::spot tail droop" is not defined}}

test dmethod-4.8 {"*" in the wrong spot} {} {
    cleanup

    catch {
        type dog {
            delegate method {tail * wag} to tail
        }
    } result

    set result
} {Error in "delegate method {tail * wag}...", "*" must be the last token.}

test dmethod-5.1 {prefix/method collision} {} {
    cleanup

    catch {
        type dog {
            delegate method wag to tail
            delegate method {wag tail} to tail as wag
        }
    } result

    set result
} {Error in "delegate method {wag tail}...", "wag" has no submethods.}

test dmethod-5.2 {prefix/method collision} {} {
    cleanup

    catch {
        type dog {
            delegate method {wag tail} to tail as wag
            delegate method wag to tail
        }
    } result

    set result
} {Error in "delegate method wag...", "wag" has submethods.}

test dmethod-5.3 {prefix/method collision} {} {
    cleanup

    catch {
        type dog {
            delegate method {wag tail} to tail
            delegate method {wag tail proudly} to tail as wag
        }
    } result

    set result
} {Error in "delegate method {wag tail proudly}...", "wag tail" has no submethods.}

test dmethod-5.4 {prefix/method collision} {} {
    cleanup

    catch {
        type dog {
            delegate method {wag tail proudly} to tail as wag
            delegate method {wag tail} to tail
        }
    } result

    set result
} {Error in "delegate method {wag tail}...", "wag tail" has submethods.}

#-----------------------------------------------------------------------
# delegated options

test doption-1.1 {delegate option to non-existent component} {} {
    cleanup

    set result ""

    type dog {
        delegate option -foo to bar
    }

    dog create spot
    catch {spot cget -foo} result
    set result
} {component "bar" is undefined in ::dog ::spot}

test doption-1.2 {delegating option to existing component: cget} {
    cleanup

    type cat {
        option -color "black"
    }

    cat create hershey

    type dog {
        constructor {args} {
            set catthing ::hershey
        }

        delegate option -color to catthing
    }

    dog create spot
    spot cget -color
} {black}

test doption-1.3 {delegating option to existing component: configure} {
    cleanup

    type cat {
        option -color "black"
    }

    cat create hershey

    type dog {
        constructor {args} {
            set catthing ::hershey
            $self configurelist $args
        }

        delegate option -color to catthing
    }

    dog create spot -color blue
    list [spot cget -color] [hershey cget -color]
} {blue blue}

test doption-1.4 {delegating unknown options to existing component} {
    cleanup

    type cat {
        option -color "black"
    }

    cat create hershey

    type dog {
        constructor {args} {
            set catthing ::hershey
            
            # Note: must do this after components are defined; this
            # may be a problem.
            $self configurelist $args
        }

        delegate option * to catthing
    }

    dog create spot -color blue
    list [spot cget -color] [hershey cget -color]
} {blue blue}

test doption-1.5 {can't oncget for delegated option} {
    cleanup

    set result ""
    catch {
        type dog {
            delegate option -color to catthing

            oncget -color { }
        }
    } result
    set result
} {Error in "oncget -color...", option "-color" is delegated}

test doption-1.6 {can't onconfigure for delegated option} {
    cleanup

    set result ""
    catch {
        type dog {
            delegate option -color to catthing

            onconfigure -color {value} { }
        }
    } result
    set result
} {onconfigure -color: option "-color" is delegated}

test doption-1.7 {delegating unknown options to existing component: error} {
    cleanup

    type cat {
        option -color "black"
    }

    cat create hershey

    type dog {
        constructor {args} {
            set catthing ::hershey
            $self configurelist $args
        }

        delegate option * to catthing
    }
    
    set result {}
    catch {dog create spot -colour blue} result
    set result
} {Error in constructor: unknown option "-colour"}

test doption-1.8 {can't delegate local option: order 1} {
    cleanup

    catch {
        type cat {
            option -color "black"
            delegate option -color to hull
        }
    } result

    set result
} {Error in "delegate option -color...", "-color" has been defined locally}

test doption-1.9 {can't delegate local option: order 2} {
    cleanup

    catch {
        type cat {
            delegate option -color to hull
            option -color "black"
        }
    } result

    set result
} {Error in "option -color...", cannot define "-color" locally, it has been delegated}

test doption-1.10 {excepted options are caught properly on cget} {
    cleanup

    type tail {
        option -a a
        option -b b
        option -c c
    }

    type cat {
        delegate option * to tail except {-b -c}

        constructor {args} {
            set tail [tail %AUTO%]
        }
    }

    cat fifi

    catch {fifi cget -a} a
    catch {fifi cget -b} b
    catch {fifi cget -c} c
    
    list $a $b $c
} {a {unknown option "-b"} {unknown option "-c"}}

test doption-1.11 {excepted options are caught properly on configurelist} {
    cleanup

    type tail {
        option -a a
        option -b b
        option -c c
    }

    type cat {
        delegate option * to tail except {-b -c}

        constructor {args} {
            set tail [tail %AUTO%]
        }
    }

    cat fifi

    catch {fifi configurelist {-a 1}} a
    catch {fifi configurelist {-b 1}} b
    catch {fifi configurelist {-c 1}} c
    
    list $a $b $c
} {{} {unknown option "-b"} {unknown option "-c"}}

test doption-1.12 {excepted options are caught properly on configure, 1} {
    cleanup

    type tail {
        option -a a
        option -b b
        option -c c
    }

    type cat {
        delegate option * to tail except {-b -c}

        constructor {args} {
            set tail [tail %AUTO%]
        }
    }

    cat fifi

    catch {fifi configure -a 1} a
    catch {fifi configure -b 1} b
    catch {fifi configure -c 1} c
    
    list $a $b $c
} {{} {unknown option "-b"} {unknown option "-c"}}

test doption-1.13 {excepted options are caught properly on configure, 2} {
    cleanup

    type tail {
        option -a a
        option -b b
        option -c c
    }

    type cat {
        delegate option * to tail except {-b -c}

        constructor {args} {
            set tail [tail %AUTO%]
        }
    }

    cat fifi

    catch {fifi configure -a} a
    catch {fifi configure -b} b
    catch {fifi configure -c} c
    
    list $a $b $c
} {{-a a A a a} {unknown option "-b"} {unknown option "-c"}}

test doption-1.14 {configure query skips excepted options} {
    cleanup

    type tail {
        option -a a
        option -b b
        option -c c
    }

    type cat {
        option -d d
        delegate option * to tail except {-b -c}

        constructor {args} {
            set tail [tail %AUTO%]
        }
    }

    cat fifi

    fifi configure
} {{-d d D d d} {-a a A a a}}


#-----------------------------------------------------------------------
# from

test from-1.1 {getting default values} {
    cleanup

    type dog {
        option -foo FOO
        option -bar BAR

        constructor {args} {
            $self configure -foo  [from args -foo AAA]
            $self configure -bar  [from args -bar]
        }
    }
    
    dog create spot
    list [spot cget -foo] [spot cget -bar]
} {AAA BAR}

test from-1.2 {getting non-default values} {
    cleanup

    type dog {
        option -foo FOO
        option -bar BAR
        option -args

        constructor {args} {
            $self configure -foo [from args -foo]
            $self configure -bar [from args -bar]
            $self configure -args $args
        }
    }
    
    dog create spot -foo quux -baz frobnitz -bar frobozz
    list [spot cget -foo] [spot cget -bar] [spot cget -args] 
} {quux frobozz {-baz frobnitz}}

#-----------------------------------------------------------------------
# Widgetadaptors


test widgetadaptor-1.1 {creating a widget: hull hijacking} tk {
    cleanup

    widgetadaptor mylabel {
        constructor {args} {
            installhull [label $self]
            $self configurelist $args
        }

        method hull {} { return $hull] }

        delegate method * to hull
        delegate option * to hull
    }
    
    mylabel create .label -text "My Label"

    set a [.label cget -text]
    set b [hull1.label cget -text]

    destroy .label
    tkbide
    list $a $b
} {{My Label} {My Label}}

test widgetadaptor-1.2 {destroying a widget with destroy} tk {
    cleanup

    widgetadaptor mylabel {
        constructor {} {
            installhull [label $self]
        }
    }
    
    mylabel create .label
    set a [namespace children ::mylabel]
    destroy .label
    set b [namespace children ::mylabel]
    tkbide
    list $a $b
} {::mylabel::Snit_inst1 {}}

test widgetadaptor-1.3 {destroying two widgets of the same type with destroy} tk {
    cleanup

    widgetadaptor mylabel {
        constructor {} {
            installhull [label $self]
        }
    }
    
    mylabel create .lab1
    mylabel create .lab2
    set a [namespace children ::mylabel]
    destroy .lab1
    destroy .lab2
    set b [namespace children ::mylabel]
    tkbide
    list $a $b
} {{::mylabel::Snit_inst1 ::mylabel::Snit_inst2} {}}

test widgetadaptor-1.4 {destroying a widget with rename, then destroy type} tk {
    cleanup

    widgetadaptor mylabel {
        constructor {} {
            installhull [label $self]
        }
    }

    mylabel create .label
    set a [namespace children ::mylabel]
    rename .label ""
    set b [namespace children ::mylabel]

    mylabel destroy
    tkbide
    list $a $b
} {::mylabel::Snit_inst1 {}}

test widgetadaptor-1.5 {destroying two widgets of the same type with rename} tk {
    cleanup

    widgetadaptor mylabel {
        constructor {} {
            installhull [label $self]
        }
    }
    
    mylabel create .lab1
    mylabel create .lab2
    set a [namespace children ::mylabel]
    rename .lab1 ""
    rename .lab2 ""
    set b [namespace children ::mylabel]
    mylabel destroy
    tkbide
    list $a $b
} {{::mylabel::Snit_inst1 ::mylabel::Snit_inst2} {}}

test widgetadaptor-1.6 {create/destroy twice, with destroy} tk {
    cleanup

    widgetadaptor mylabel {
        constructor {} {
            installhull [label $self]
        }
    }
    
    mylabel create .lab1
    set a [namespace children ::mylabel]
    destroy .lab1

    mylabel create .lab1
    set b [namespace children ::mylabel]
    destroy .lab1

    set c [namespace children ::mylabel]
    mylabel destroy
    tkbide
    list $a $b $c
} {::mylabel::Snit_inst1 ::mylabel::Snit_inst2 {}}

test widgetadaptor-1.7 {create/destroy twice, with rename} tk {
    cleanup

    widgetadaptor mylabel {
        constructor {} {
            installhull [label $self]
        }
    }
    
    mylabel create .lab1
    set a [namespace children ::mylabel]
    rename .lab1 ""

    mylabel create .lab1
    set b [namespace children ::mylabel]
    rename .lab1 ""

    set c [namespace children ::mylabel]
    mylabel destroy
    tkbide
    list $a $b $c
} {::mylabel::Snit_inst1 ::mylabel::Snit_inst2 {}}

test widgetadaptor-1.8 {"create" is optional} tk {
    cleanup

    widgetadaptor mylabel {
        constructor {args} {
            installhull [label $self]
        }
        method howdy {} {return "Howdy!"}
    }
    
    mylabel .label
    set a [.label howdy]

    destroy .label
    tkbide
    set a
} {Howdy!}

test widgetadaptor-1.9 {"create" is optional, but must be a valid name} tk {
    cleanup

    widgetadaptor mylabel {
        constructor {args} {
            installhull [label $self]
        }
        method howdy {} {return "Howdy!"}
    }
    
    catch {mylabel foo} result
    tkbide
    set result
} {"::mylabel foo" is not defined}

test widgetadaptor-1.10 {user-defined destructors are called} tk {
    cleanup

    widgetadaptor mylabel { 
        typevariable flag ""
        
        constructor {args} {
            installhull [label $self]
            set flag "created $self"
        }

        destructor {
            set flag "destroyed $self"
        }

        typemethod getflag {} {
            return $flag
        }
    }
    
    mylabel .label
    set a [mylabel getflag]
    destroy .label
    tkbide
    list $a [mylabel getflag]
} {{created .label} {destroyed .label}}

test widgetadaptor-1.11 {destroy method not defined for widget types} tk {
    cleanup

    widgetadaptor mylabel { 
        constructor {args} {
            installhull [label $self]
        }
    }
    
    mylabel .label
    catch {.label destroy} result
    destroy .label
    tkbide
    set result
} {".label destroy" is not defined}

test widgetadaptor-1.12 {hull can be repeatedly renamed} tk {
    cleanup

    widgetadaptor basetype { 
        constructor {args} {
            installhull [label $self]
        }

        method basemethod {} { return "basemethod" }
    }

    widgetadaptor w1 {
        constructor {args} {
            installhull [basetype create $self]
        }
    }

    widgetadaptor w2 { 
        constructor {args} {
            installhull [w1 $self]
        }
    }

    set a [w2 .foo]
    tkbide
    set a
} {.foo}

test widgetadaptor-1.13 {widget names can be generated} tk {
    cleanup

    # Don't use this widget type name in any other test.
    widgetadaptor unique {
        constructor {args} {
            installhull [label $self]
        }
    }

    set w [unique .%AUTO%]
    destroy $w
    tkbide
    set w
} {.unique1}

test widgetadaptor-1.14 {snit::widgetadaptor as hull} tk {
    cleanup

    widgetadaptor mylabel {
        constructor {args} {
            installhull [label $self]
            $self configurelist $args
        }
        method method1 {} {
            return "method1"
        }
        delegate option * to hull
    }

    widgetadaptor mylabel2 {
        constructor {args} {
            installhull [mylabel $self]
            $self configurelist $args
        }
        method method2 {} {
            return "method2: [$hull method1]"
        }
        delegate option * to hull
    }

    mylabel2 .label -text "Some Text"
    set a [.label method2]
    set b [.label cget -text]
    .label configure -text "More Text"
    set c [.label cget -text]
    set d [namespace children ::mylabel2]
    set e [namespace children ::mylabel]

    destroy .label

    set f [namespace children ::mylabel2]
    set g [namespace children ::mylabel]

    mylabel2 destroy
    mylabel destroy

    tkbide
    list $a $b $c $d $e $f $g
} {{method2: method1} {Some Text} {More Text} ::mylabel2::Snit_inst1 ::mylabel::Snit_inst1 {} {}}

test widgetadaptor-1.15 {snit::widgetadaptor as hull; use rename} tk {
    cleanup

    widgetadaptor mylabel {
        constructor {args} {
            installhull [label $self]
            $self configurelist $args
        }
        method method1 {} {
            return "method1"
        }
        delegate option * to hull
    }

    widgetadaptor mylabel2 {
        constructor {args} {
            installhull [mylabel $self]
            $self configurelist $args
        }
        method method2 {} {
            return "method2: [$hull method1]"
        }
        delegate option * to hull
    }

    mylabel2 .label -text "Some Text"
    set a [.label method2]
    set b [.label cget -text]
    .label configure -text "More Text"
    set c [.label cget -text]
    set d [namespace children ::mylabel2]
    set e [namespace children ::mylabel]

    rename .label ""

    set f [namespace children ::mylabel2]
    set g [namespace children ::mylabel]

    mylabel2 destroy
    mylabel destroy

    tkbide
    list $a $b $c $d $e $f $g
} {{method2: method1} {Some Text} {More Text} ::mylabel2::Snit_inst1 ::mylabel::Snit_inst1 {} {}}

test widgetadaptor-1.16 {BWidget Label as hull} bwidget {
    cleanup

    widgetadaptor mylabel {
        constructor {args} {
            installhull [Label $win]
            $self configurelist $args
        }
        delegate option * to hull
    }

    mylabel .label -text "Some Text"
    set a [.label cget -text]

    .label configure -text "More Text"
    set b [.label cget -text]

    set c [namespace children ::mylabel]

    destroy .label

    set d [namespace children ::mylabel]

    mylabel destroy

    tkbide
    list $a $b $c $d
} {{Some Text} {More Text} ::mylabel::Snit_inst1 {}}

test widgetadaptor-1.17 {error in widgetadaptor constructor} tk {
    cleanup

    widgetadaptor mylabel {
        constructor {args} {
            error "Simulated Error"
        }
    }
    
    catch {mylabel .lab} result

    set result
} {Error in constructor: Simulated Error}


#-----------------------------------------------------------------------
# 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} tk {
    cleanup

    widget myframe {
        method hull {} { return $hull }

        delegate method * to hull
        delegate option * to hull
    }
    
    myframe create .frm -background green

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

    destroy .frm
    tkbide
    list $a $b
} {green ::hull1.frm}

test widget-2.1 {can't redefine hull} tk {
    cleanup

    widget myframe {
        method resethull {} { set hull "" }
    }
    
    myframe .frm

    catch {.frm resethull} result
    set result
} {can't set "hull": The 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} tk {
    cleanup

    widget myframe {
        # Delegate an option just to make sure the component variable
        # exists.
        delegate option -font to text

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

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

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

test install-1.2 {installed components are saved properly} tk {
    cleanup

    widget myframe {
        # Delegate an option just to make sure the component variable
        # exists.
        delegate option -font to text

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

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

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

test install-1.3 {can't install until hull exists} tk {
    cleanup

    widgetadaptor myframe {
        # Delegate an option just to make sure the component variable
        # exists.
        delegate option -font to text

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

    catch {
        myframe .frm
    } result

    set result
} {Error in constructor: tried to install "text" before the hull exists}

test install-1.4 {install queries option database} tk {
    cleanup

    widget myframe {
        delegate option -font to text

        typeconstructor {
            option add *Myframe.font Courier
        }

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

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

test install-1.5 {explicit options override option database} tk {
    cleanup

    widget myframe {
        delegate option -font to text

        typeconstructor {
            option add *Myframe.font Courier
        }

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

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

test install-1.6 {option db works with targetted options} tk {
    cleanup

    widget myframe {
        delegate option -textfont to text as -font

        typeconstructor {
            option add *Myframe.textfont Courier
        }

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

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

test install-1.7 {install works for snit::types} {
    cleanup

    type tail {
        option -tailcolor black
    }

    type dog {
        delegate option -tailcolor to tail

        constructor {args} {
            install tail using tail $self.tail
        }
    }

    dog fido
    fido cget -tailcolor
} {black}

test install-1.8 {install can install non-widget components} tk {
    cleanup

    type dog {
        option -tailcolor black
    }

    widget myframe {
        delegate option -tailcolor to thedog

        typeconstructor {
            option add *Myframe.tailcolor green
        }

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

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

} {green}

test install-1.9 {ok if no options are delegated to component} tk {
    cleanup

    type dog {
        option -tailcolor black
    }

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

    myframe .frm
    destroy .frm
    tkbide

    # Test passes if no error is raised.
    list ok
} {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.
} tk {
    cleanup

    widget myframe {
        delegate option * to text

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

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

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

    list $a $b
} {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.
} tk {
    cleanup

    widget myframe {
        option -foreground white
        delegate option * to text

        typeconstructor {
            option add *Myframe.foreground red
        }

        constructor {args} {
            install 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 {$a ne $b}
} {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.
} tk {
    cleanup

    widget myframe {
        delegate option * to text

        typeconstructor {
            option add *Myframe.foreground red
        }

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

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

    set a
} {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.
} tk {
    cleanup

    widget myframe {
        delegate option * to text except -state

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

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

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

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

    set a
} {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 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.
} tk {
    cleanup

    widget myframe {
        delegate option -background to hull

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

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

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

test installhull-1.2 {
    Options delegated to a widget's hull frame with a different name are 
    initialized from the option database.
} tk {
    cleanup

    widget myframe {
        delegate option -mainbackground to hull as -background

        typeconstructor {
            option add *Myframe.mainbackground red
        }
    }

    myframe .frm
    set a [.frm cget -mainbackground]
    destroy .frm
    tkbide
    set a
} {red}

test installhull-1.3 {
    options delegated to a widgetadaptor's hull frame with the same name are 
    initialized from the option database.  Note that there's no 
    explicit code in Snit to do this; there's no way to change the
    adapted hull widget's -class, so the widget is simply being
    initialized normally.
} tk {
    cleanup

    widgetadaptor myframe {
        delegate option -background to hull

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

        constructor {args} {
            installhull using frame
        }

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

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

test installhull-1.4 {
    Options delegated to a widget's hull frame with a different name are 
    initialized from the option database.
} tk {
    cleanup

    widgetadaptor myframe {
        delegate option -mainbackground to hull as -background

        typeconstructor {
            option add *Frame.mainbackground red
        }

        constructor {args} {
            installhull using frame
        }
    }

    myframe .frm
    set a [.frm cget -mainbackground]
    destroy .frm
    tkbide
    set a
} {red}

test installhull-1.4 {
    Option values read from the option database are overridden by options
    explicitly passed, even if delegated under a different name.
} tk {
    cleanup

    widgetadaptor myframe {
        delegate option -mainbackground to hull as -background

        typeconstructor {
            option add *Frame.mainbackground red
            option add *Frame.width 123
        }

        constructor {args} {
            installhull using frame -background green -width 321
        }

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

    myframe .frm
    set a [.frm cget -mainbackground]
    set b [.frm getwid]
    destroy .frm
    tkbide
    list $a $b
} {green 321}


#-----------------------------------------------------------------------
# Instance Introspection

test iinfo-1.1 {object info too few args} {
    cleanup

    type dog { }

    dog create spot

    catch {spot info} result
    set result
} {wrong # args: should be "::snit::RT.method.info type selfns win self command args"}

test iinfo-1.2 {object info too many args} {
    cleanup

    type dog { }

    dog create spot

    catch {spot info type foo} result
    set result
} {wrong # args: should be "::snit::RT.method.info.type type selfns win self"}

test iinfo-2.1 {object info type} {
    cleanup

    type dog { }

    dog create spot
    spot info type
} {::dog}

test iinfo-3.1 {object info typevars} {
    cleanup

    type dog { 
        typevariable thisvar 1

        constructor {args} {
            typevariable thatvar 2
        }
    }

    dog create spot
    lsort [spot info typevars]
} {::dog::thatvar ::dog::thisvar}

test iinfo-3.2 {object info typevars with pattern} {
    cleanup

    type dog { 
        typevariable thisvar 1

        constructor {args} {
            typevariable thatvar 2
        }
    }

    dog create spot
    spot info typevars *this*
} {::dog::thisvar}

test iinfo-4.1 {object info vars} {
    cleanup

    type dog { 
        variable hisvar 1

        constructor {args} {
            variable hervar
            set hervar 2
        }
    }

    dog create spot
    lsort [spot info vars]
} {::dog::Snit_inst1::hervar ::dog::Snit_inst1::hisvar}

test iinfo-4.2 {object info vars with pattern} {
    cleanup

    type dog { 
        variable hisvar 1

        constructor {args} {
            variable hervar
            set hervar 2
        }
    }

    dog create spot
    spot info vars "*his*"
} {::dog::Snit_inst1::hisvar}

test iinfo-5.1 {object info no vars defined} {
    cleanup

    type dog { }

    dog create spot
    list [spot info vars] [spot info typevars]
} {{} {}}

test iinfo-6.1 {info options with no options} {
    cleanup

    type dog { }
    dog create spot

    llength [spot info options]
} {0}

test iinfo-6.2 {info options with only local options} {
    cleanup

    type dog { 
        option -foo a
        option -bar b
    }
    dog create spot

    lsort [spot info options]
} {-bar -foo}

test iinfo-6.3 {info options with local and delegated options} {
    cleanup

    type dog { 
        option -foo a
        option -bar b
        delegate option -quux to sibling
    }
    dog create spot

    lsort [spot info options]
} {-bar -foo -quux}

test iinfo-6.4 {info options with unknown delegated options} tk {
    cleanup

    widgetadaptor myframe { 
        option -foo a
        delegate option * to hull
        constructor {args} {
            installhull [frame $self]
        }
    }
    myframe .frm

    set a [lsort [.frm info options]]
    destroy .frm
    tkbide
    set a
} {-background -bd -bg -borderwidth -class -colormap -container -cursor -foo -height -highlightbackground -highlightcolor -highlightthickness -padx -pady -relief -takefocus -visual -width}

test iinfo-6.5 {info options with exceptions} tk {
    cleanup

    widgetadaptor myframe { 
        option -foo a
        delegate option * to hull except -background
        constructor {args} {
            installhull [frame $self]
        }
    }
    myframe .frm

    set a [lsort [.frm info options]]
    destroy .frm
    tkbide
    set a
} {-bd -bg -borderwidth -class -colormap -container -cursor -foo -height -highlightbackground -highlightcolor -highlightthickness -padx -pady -relief -takefocus -visual -width}

test iinfo-6.6 {info options with pattern} tk {
    cleanup

    widgetadaptor myframe { 
        option -foo a
        delegate option * to hull
        constructor {args} {
            installhull [frame $self]
        }
    }
    myframe .frm

    set a [lsort [.frm info options -c*]]
    destroy .frm
    tkbide
    set a
} {-class -colormap -container -cursor}

test iinfo-7.1 {info typemethods, simple case} {
    cleanup

    type dog { }

    dog spot

    lsort [spot info typemethods]
} {create destroy info}

test iinfo-7.2 {info typemethods, with pattern} {
    cleanup

    type dog { }

    dog spot

    spot info typemethods i*
} {info}

test iinfo-7.3 {info typemethods, with explicit typemethods} {
    cleanup

    type dog { 
        typemethod foo {} {}
        delegate typemethod bar to comp
    }

    dog spot

    lsort [spot info typemethods]
} {bar create destroy foo info}

test iinfo-7.4 {info typemethods, with implicit typemethods} {
    cleanup

    type dog { 
        delegate typemethod * to comp

        typeconstructor {
            set comp string
        }
    }

    dog create spot

    set a [lsort [spot info typemethods]]

    dog length foo
    dog is boolean yes

    set b [lsort [spot info typemethods]]

    set c [spot info typemethods len*]

    list $a $b $c
} {{create destroy info} {create destroy info is length} length}

test iinfo-7.5 {info typemethods, with hierarchical typemethods} {
    cleanup

    type dog { 
        delegate typemethod {comp foo} to comp
        
        typemethod {comp bar} {} {}
    }

    dog create spot

    lsort [spot info typemethods]
} {{comp bar} {comp foo} create destroy info}


test iinfo-8.1 {info methods, simple case} {
    cleanup

    type dog { }

    dog spot

    lsort [spot info methods]
} {destroy info}

test iinfo-8.2 {info methods, with pattern} {
    cleanup

    type dog { }

    dog spot

    spot info methods i*
} {info}

test iinfo-8.3 {info methods, with explicit methods} {
    cleanup

    type dog { 
        method foo {} {}
        delegate method bar to comp
    }

    dog spot

    lsort [spot info methods]
} {bar destroy foo info}

test iinfo-8.4 {info methods, with implicit methods} {
    cleanup

    type dog { 
        delegate method * to comp

        constructor {args} {
            set comp string
        }
    }

    dog create spot

    set a [lsort [spot info methods]]

    spot length foo
    spot is boolean yes

    set b [lsort [spot info methods]]

    set c [spot info methods len*]

    list $a $b $c
} {{destroy info} {destroy info is length} length}

test iinfo-8.5 {info methods, with hierarchical methods} {
    cleanup

    type dog { 
        delegate method {comp foo} to comp
        
        method {comp bar} {} {}
    }

    dog create spot

    lsort [spot info methods]
} {{comp bar} {comp foo} destroy info}


#-----------------------------------------------------------------------
# Type Introspection

test tinfo-1.1 {type info too few args} {
    cleanup

    type dog { }

    catch {dog info} result
    set result
} {wrong # args: should be "::snit::RT.typemethod.info type command args"}

test tinfo-1.2 {type info too many args} {
    cleanup

    type dog { }

    catch {dog info instances foo bar} result
    set result
} {wrong # args: should be "::snit::RT.typemethod.info.instances type ?pattern?"}

test tinfo-2.1 {type info typevars} {
    cleanup

    type dog { 
        typevariable thisvar 1

        constructor {args} {
            typevariable thatvar 2
        }
    }

    dog create spot
    lsort [dog info typevars]
} {::dog::thatvar ::dog::thisvar}

test tinfo-3.1 {type info instances} {
    cleanup

    type dog { }

    dog create spot
    dog create fido

    lsort [dog info instances]
} {::fido ::spot}

test tinfo-3.2 {widget info instances} tk {
    cleanup

    widgetadaptor mylabel { 
        constructor {args} {
            installhull [label $self]
        }
    }

    mylabel .lab1
    mylabel .lab2

    set result [mylabel info instances]

    destroy .lab1
    destroy .lab2
    tkbide

    lsort $result
} {.lab1 .lab2}

test tinfo-3.3 {type info instances with non-global namespaces} {
    cleanup

    type dog { }

    dog create ::spot

    namespace eval ::dogs:: {
        set ::qname [dog create fido]
    }

    list $qname [lsort [dog info instances]]
} {::dogs::fido {::dogs::fido ::spot}}

test tinfo-3.4 {type info instances with pattern} {
    cleanup

    type dog { }

    dog create spot
    dog create fido

    dog info instances "*f*"
} {::fido}

test tinfo-4.1 {type info typevars with pattern} {
    cleanup

    type dog { 
        typevariable thisvar 1

        constructor {args} {
            typevariable thatvar 2
        }
    }

    dog create spot
    dog info typevars *this*
} {::dog::thisvar}

test tinfo-5.1 {type info typemethods, simple case} {
    cleanup

    type dog { }

    lsort [dog info typemethods]
} {create destroy info}

test tinfo-5.2 {type info typemethods, with pattern} {
    cleanup

    type dog { }

    dog info typemethods i*
} {info}

test tinfo-5.3 {type info typemethods, with explicit typemethods} {
    cleanup

    type dog { 
        typemethod foo {} {}
        delegate typemethod bar to comp
    }

    lsort [dog info typemethods]
} {bar create destroy foo info}

test tinfo-5.4 {type info typemethods, with implicit typemethods} {
    cleanup

    type dog { 
        delegate typemethod * to comp

        typeconstructor {
            set comp string
        }
    }

    set a [lsort [dog info typemethods]]

    dog length foo
    dog is boolean yes

    set b [lsort [dog info typemethods]]

    set c [dog info typemethods len*]

    list $a $b $c
} {{create destroy info} {create destroy info is length} length}

test tinfo-5.5 {info typemethods, with hierarchical typemethods} {
    cleanup

    type dog { 
        delegate typemethod {comp foo} to comp
        
        typemethod {comp bar} {} {}
    }

    lsort [dog info typemethods]
} {{comp bar} {comp foo} create destroy info}


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

test widgetclass-1.1 {can't set widgetclass for snit::types} {
    cleanup

    catch {
        type dog { 
            widgetclass Dog
        }
    } result
    
    set result
} {widgetclass cannot be set for snit::types}

test widgetclass-1.2 {can't set widgetclass for snit::widgetadaptors} tk {
    cleanup

    catch {
        widgetadaptor dog { 
            widgetclass Dog
        }
    } result
    
    set result
} {widgetclass cannot be set for snit::widgetadaptors}

test widgetclass-1.3 {widgetclass must begin with uppercase letter} tk {
    cleanup

    catch {
        widget dog { 
            widgetclass dog
        }
    } result
    
    set result
} {widgetclass "dog" does not begin with an uppercase letter}

test widgetclass-1.4 {widgetclass can only be defined once} tk {
    cleanup

    catch {
        widget dog { 
            widgetclass Dog
            widgetclass Dog
        }
    } result
    
    set result
} {too many widgetclass statements}

test widgetclass-1.5 {widgetclass set successfully} tk {
    cleanup

    widget dog { 
        widgetclass DogWidget
    }
    
    # The test passes if no error is thrown.
    list ok
} {ok}

test widgetclass-1.6 {implicit widgetclass applied to hull} tk {
    cleanup

    widget dog { 
        typeconstructor {
            option add *Dog.background green 
        }

        method background {} {
            $hull cget -background
        }
    }

    dog .dog
    
    set bg [.dog background]

    destroy .dog
    
    set bg
} {green}

test widgetclass-1.7 {explicit widgetclass applied to hull} tk {
    cleanup

    widget dog { 
        widgetclass DogWidget

        typeconstructor {
            option add *DogWidget.background green 
        }

        method background {} {
            $hull cget -background
        }
    }

    dog .dog
    
    set bg [.dog background]

    destroy .dog
    
    set bg
} {green}

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

test hulltype-1.1 {can't set hulltype for snit::types} {
    cleanup

    catch {
        type dog { 
            hulltype Dog
        }
    } result
    
    set result
} {hulltype cannot be set for snit::types}

test hulltype-1.2 {can't set hulltype for snit::widgetadaptors} tk {
    cleanup

    catch {
        widgetadaptor dog { 
            hulltype Dog
        }
    } result
    
    set result
} {hulltype cannot be set for snit::widgetadaptors}

test hulltype-1.3 {hulltype can be frame} tk {
    cleanup

    widget dog { 
        delegate option * to hull
        hulltype frame
    }

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

    set result
} {unknown option "-use"}

test hulltype-1.4 {hulltype can be toplevel} tk {
    cleanup

    widget dog { 
        delegate option * to hull
        hulltype toplevel
    }

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

    set result
} {-use use Use {} {}}

test hulltype-1.5 {hulltype can only be defined once} tk {
    cleanup

    catch {
        widget dog { 
            hulltype frame
            hulltype toplevel
        }
    } result
    
    set result
} {too many hulltype statements}

#-----------------------------------------------------------------------
# expose statement

test expose-1.1 {can't expose nothing} {
    cleanup

    catch {
        type dog { 
            expose
        }
    } result
    
    set result
} {wrong # args: should be "::snit::Comp.statement.expose component ?as? ?methodname?"}


test expose-1.2 {expose a component that's never installed} {
    cleanup

    type dog { 
        expose tail
    }

    dog fido
    
    catch {
        fido tail wag
    } result
    
    set result
} {undefined component "tail"}

test expose-1.3 {exposed method returns component command} {
    cleanup

    type tail {  }

    type dog { 
        expose tail

        constructor {} {
            install tail using tail $self.tail
        }

        destructor {
            $tail destroy
        }
    }

    dog fido
    
    fido tail
} {::fido.tail}

test expose-1.4 {exposed method calls component methods} {
    cleanup

    type tail {
        method wag   {args} {return "wag<$args>"}
        method droop {}     {return "droop"}
    }

    type dog { 
        expose tail

        constructor {} {
            install tail using tail $self.tail
        }

        destructor {
            $tail destroy
        }
    }

    dog fido
    
    list [fido tail wag] [fido tail wag abc] [fido tail wag abc def] \
        [fido tail droop]
} {wag<> wag<abc> {wag<abc def>} droop}

#-----------------------------------------------------------------------
# Error handling
#
# This section verifies that errorInfo and errorCode are propagated
# appropriately on error.

test error-1.1 {typemethod errors propagate properly} {
    cleanup

    type dog {
        typemethod generr {} {
            error bogusError bogusInfo bogusCode
        }
    }

    catch {dog generr} result

    global errorInfo errorCode

    list $result [string match "*bogusInfo*" $errorInfo] $errorCode
} {bogusError 1 bogusCode}

test error-1.2 {snit::type constructor errors propagate properly} {
    cleanup

    type dog {
        constructor {} {
            error bogusError bogusInfo bogusCode
        }
    }

    catch {dog fido} result

    global errorInfo errorCode

    list $result [string match "*bogusInfo*" $errorInfo] $errorCode
} {{Error in constructor: bogusError} 1 bogusCode}

test error-1.3 {snit::widget constructor errors propagate properly} tk {
    cleanup

    widget dog {
        constructor {args} {
            error bogusError bogusInfo bogusCode
        }
    }

    catch {dog .fido} result

    global errorInfo errorCode

    list $result [string match "*bogusInfo*" $errorInfo] $errorCode
} {{Error in constructor: bogusError} 1 bogusCode}

test error-1.4 {method errors propagate properly} {
    cleanup

    type dog {
        method generr {} {
            error bogusError bogusInfo bogusCode
        }
    }

    dog fido
    catch {fido generr} result

    global errorInfo errorCode

    list $result [string match "*bogusInfo*" $errorInfo] $errorCode
} {bogusError 1 bogusCode}

test error-1.5 {onconfigure errors propagate properly} {
    cleanup

    type dog {
        option -generr

        onconfigure -generr {value} {
            error bogusError bogusInfo bogusCode
        }
    }

    dog fido
    catch {fido configure -generr 0} result

    global errorInfo errorCode

    list $result [string match "*bogusInfo*" $errorInfo] $errorCode
} {bogusError 1 bogusCode}

test error-1.6 {oncget errors propagate properly} {
    cleanup

    type dog {
        option -generr

        oncget -generr {
            error bogusError bogusInfo bogusCode
        }
    }

    dog fido
    catch {fido cget -generr} result

    global errorInfo errorCode

    list $result [string match "*bogusInfo*" $errorInfo] $errorCode
} {bogusError 1 bogusCode}

#-----------------------------------------------------------------------
# Externally defined typemethods

test etypemethod-1.1 {external typemethods can be called as expected} {
    cleanup

    type dog { }
    typemethod dog foo {a} {return "+$a+"}
    
    dog foo bar
} {+bar+}

test etypemethod-1.2 {external typemethods can use typevariables} {
    cleanup

    type dog { 
        typevariable somevar "Howdy"
    }
    typemethod dog getvar {} {return $somevar}

    dog getvar
} {Howdy}

test etypemethod-1.3 {typemethods can be redefined dynamically} {
    cleanup

    type dog { 
        typemethod foo {} { return "foo" }
    }
    set a [dog foo]
    
    typemethod dog foo {} { return "bar"}
        
    set b [dog foo]
        
    list $a $b
} {foo bar}

test etypemethod-1.4 {can't define external typemethod if no type} {
    cleanup

    catch {
        typemethod extremelyraredog foo {} { return "bar"}
    } result 

    set result
} {no such type: "extremelyraredog"}

test etypemethod-2.1 {external hierarchical method, two tokens} {} {
    cleanup

    type dog { }
    typemethod dog {wag tail} {} {
        return "wags tail"
    }

    dog wag tail
} {wags tail}

test etypemethod-2.2 {external hierarchical method, three tokens} {} {
    cleanup

    type dog { }
    typemethod dog {wag tail proudly} {} {
        return "wags tail proudly"
    }

    dog wag tail proudly
} {wags tail proudly}

test etypemethod-2.3 {external hierarchical method, three tokens} {} {
    cleanup

    type dog { }
    typemethod dog {wag tail really high} {} {
        return "wags tail really high"
    }

    dog wag tail really high
} {wags tail really high}

test etypemethod-2.4 {redefinition is OK} {} {
    cleanup

    type dog { }
    typemethod dog {wag tail} {} {
        return "wags tail"
    }
    typemethod dog {wag tail} {} {
        return "wags tail briskly"
    }

    dog wag tail
} {wags tail briskly}

test etypemethod-3.1 {prefix/method collision} {} {
    cleanup

    type dog { 
        typemethod wag {} {}
    }

    catch {
        typemethod dog {wag tail} {} {}
    } result

    set result
} {Cannot define "wag tail", "wag" has no submethods.}

test etypemethod-3.2 {prefix/method collision} {} {
    cleanup

    type dog {
        typemethod {wag tail} {} {}
    }

    catch {
        typemethod dog wag {} {}
    } result

    set result
} {Cannot define "wag", "wag" has submethods.}

test etypemethod-3.3 {prefix/method collision} {} {
    cleanup

    type dog {
        typemethod {wag tail} {} {}
    }

    catch {
        typemethod dog {wag tail proudly} {} {}
    } result

    set result
} {Cannot define "wag tail proudly", "wag tail" has no submethods.}

test etypemethod-3.4 {prefix/method collision} {} {
    cleanup

    type dog {
        typemethod {wag tail proudly} {} {}
    }

    catch {
        typemethod dog {wag tail} {} {}
    } result

    set result
} {Cannot define "wag tail", "wag tail" has submethods.}

#-----------------------------------------------------------------------
# Externally defined methods

test emethod-1.1 {external methods can be called as expected} {
    cleanup

    type dog { }
    method dog bark {a} {return "+$a+"}
    
    dog spot
    spot bark woof
} {+woof+}

test emethod-1.2 {external methods can use typevariables} {
    cleanup

    type dog { 
        typevariable somevar "Hello"
    }
    method dog getvar {} {return $somevar}

    dog spot
    spot getvar
} {Hello}

test emethod-1.3 {external methods can use variables} {
    cleanup

    type dog { 
        variable somevar "Greetings"
    }
    method dog getvar {} {return $somevar}

    dog spot
    spot getvar
} {Greetings}

test emethod-1.4 {methods can be redefined dynamically} {
    cleanup

    type dog { 
        method bark {} { return "woof" }
    }

    dog spot

    set a [spot bark]
    
    method dog bark {} { return "arf"}
        
    set b [spot bark]
        
    list $a $b
} {woof arf}

test emethod-1.5 {delegated methods can't be redefined} {
    cleanup

    type dog { 
        delegate method bark to someotherdog
    }

    catch {
        method dog bark {} { return "arf"}
    } result
        
    set result
} {Cannot define "bark", "bark" has been delegated}

test emethod-1.6 {can't define external method if no type} {
    cleanup

    catch {
        method extremelyraredog foo {} { return "bar"}
    } result 

    set result
} {no such type: "extremelyraredog"}

test emethod-2.1 {external hierarchical method, two tokens} {} {
    cleanup

    type dog { }
    method dog {wag tail} {} {
        return "$self wags tail."
    }

    dog spot
    spot wag tail
} {::spot wags tail.}

test emethod-2.2 {external hierarchical method, three tokens} {} {
    cleanup

    type dog { }
    method dog {wag tail proudly} {} {
        return "$self wags tail proudly."
    }

    dog spot
    spot wag tail proudly
} {::spot wags tail proudly.}

test emethod-2.3 {external hierarchical method, three tokens} {} {
    cleanup

    type dog { }
    method dog {wag tail really high} {} {
        return "$self wags tail really high."
    }

    dog spot
    spot wag tail really high
} {::spot wags tail really high.}

test emethod-2.4 {redefinition is OK} {} {
    cleanup

    type dog { }
    method dog {wag tail} {} {
        return "$self wags tail."
    }
    method dog {wag tail} {} {
        return "$self wags tail briskly."
    }

    dog spot
    spot wag tail
} {::spot wags tail briskly.}

test emethod-3.1 {prefix/method collision} {} {
    cleanup

    type dog { 
        method wag {} {}
    }

    catch {
        method dog {wag tail} {} {
            return "$self wags tail."
        }
    } result

    set result
} {Cannot define "wag tail", "wag" has no submethods.}

test emethod-3.2 {prefix/method collision} {} {
    cleanup

    type dog {
        method {wag tail} {} {
            return "$self wags tail."
        }
    }

    catch {
        method dog wag {} {}
    } result

    set result
} {Cannot define "wag", "wag" has submethods.}

test emethod-3.3 {prefix/method collision} {} {
    cleanup

    type dog {
        method {wag tail} {} {}
    }

    catch {
        method dog {wag tail proudly} {} {
            return "$self wags tail."
        }
    } result

    set result
} {Cannot define "wag tail proudly", "wag tail" has no submethods.}

test emethod-3.4 {prefix/method collision} {} {
    cleanup

    type dog {
        method {wag tail proudly} {} {
            return "$self wags tail."
        }
    }

    catch {
        method dog {wag tail} {} {}
    } result

    set result
} {Cannot define "wag tail", "wag tail" has submethods.}


#-----------------------------------------------------------------------
# Macros

test macro-1.1 {can't redefine non-macros} {
    cleanup

    catch {
        snit::macro method {} {}
    } result
    set result
} {invalid macro name "method"}

test macro-1.2 {can define and use a macro} {
    cleanup

    snit::macro hello {name} {
        method hello {} "return {Hello, $name!}"
    }

    type dog {
        hello World
    }

    dog spot

    spot hello

} {Hello, World!}

test macro-1.3 {can redefine macro} {
    cleanup

    snit::macro dup {} {}
    snit::macro dup {} {}

    set dummy "No error"
} {No error}

test macro-1.4 {can define macro in namespace} {
    cleanup

    snit::macro ::test::goodbye {name} {
        method goodbye {} "return {Goodbye, $name!}"
    }

    type dog {
        ::test::goodbye World
    }

    dog spot

    spot goodbye
} {Goodbye, World!}

test macro-1.5 {_proc and _variable are defined} {
    cleanup

    snit::macro testit {} {
        set a [info commands _variable]
        set b [info commands _proc]
        method testit {} "list $a $b"
    }

    type dog {
        testit
    }

    dog spot

    spot testit
} {_variable _proc}

test macro-1.6 {_variable works} {
    cleanup

    snit::macro test1 {} {
        _variable myvar "_variable works"
    }

    snit::macro test2 {} {
        _variable myvar

        method testit {} "return {$myvar}"
    }

    type dog {
        test1
        test2
    }

    dog spot

    spot testit
} {_variable works}

#-----------------------------------------------------------------------
# Component Statement

test component-1.1 {component defines an instance variable} {
    cleanup
    type dog {
        component tail
    }

    dog spot

    namespace tail [spot info vars tail]
} {tail}

test component-1.2 {-public exposes the component} {
    cleanup

    type tail {
        method wag {} {
            return "Wag, wag"
        }
    }

    type dog {
        component tail -public mytail

        constructor {} {
            set tail [tail %AUTO%]
        }
    }

    dog spot

    spot mytail wag
} {Wag, wag}

test component-1.3 {-inherit requires a boolean value} {
    cleanup

    catch {
        type dog {
            component animal -inherit foo
        }
    } result

    set result
} {component animal -inherit: expected boolean value, got "foo"}

test component-1.4 {-inherit delegates unknown methods to the component} {
    cleanup

    type animal {
        method eat {} {
            return "Eat, eat."
        }
    }

    type dog {
        component animal -inherit yes

        constructor {} {
            set animal [animal %AUTO%]
        }
    }

    dog spot

    spot eat
} {Eat, eat.}

test component-1.5 {-inherit delegates unknown options to the component} {
    cleanup
    
    type animal {
        option -size medium
    }

    type dog {
        component animal -inherit yes

        constructor {} {
            set animal [animal %AUTO%]
        }
    }

    dog spot

    spot cget -size
} {medium}

#-----------------------------------------------------------------------
# Typevariables, Variables, Typecomponents, Components

test typevar_var-1.1 {variable/typevariable collisions not allowed: order 1} {
    cleanup

    catch {
        type dog {
            typevariable var
            variable var
        }
    } result

    set result
} {Error in "variable var...", "var" is already a typevariable}

test typevar_var-1.2 {variable/typevariable collisions not allowed: order 2} {
    cleanup

    catch {
        type dog {
            variable var
            typevariable var
        }
    } result

    set result
} {Error in "typevariable var...", "var" is already an instance variable}

test typevar_var-1.3 {component/typecomponent collisions not allowed: order 1} {
    cleanup

    catch {
        type dog {
            typecomponent comp
            component comp
        }
    } result

    set result
} {Error in "component comp...", "comp" is already a typevariable}

test typevar_var-1.4 {component/typecomponent collisions not allowed: order 2} {
    cleanup

    catch {
        type dog {
            component comp
            typecomponent comp
        }
    } result

    set result
} {Error in "typecomponent comp...", "comp" is already an instance variable}

test typevar_var-1.5 {can't delegate options to typecomponents} {
    cleanup

    catch {
        type dog {
            typecomponent comp

            delegate option -opt to comp
        }
    } result

    set result
} {Error in "delegate option -opt...", "comp" is already a typevariable}

test typevar_var-1.5 {can't delegate typemethods to instance components} {
    cleanup

    catch {
        type dog {
            component comp

            delegate typemethod foo to comp
        }
    } result

    set result
} {Error in "delegate typemethod foo...", "comp" is already an instance variable}

test typevar_var-1.6 {can delegate methods to typecomponents} {
    cleanup

    proc echo {args} {return [join $args "|"]}

    type dog {
        typecomponent tail

        typeconstructor {
            set tail echo
        }

        delegate method wag to tail
    }

    dog spot
    spot wag briskly
} {wag|briskly}

#-----------------------------------------------------------------------
# Option syntax tests.
#
# This set of tests verifies that the option statement is interpreted
# properly, that errors are caught, and that the type's optionInfo
# array is initialized properly.
#
# TBD: At some point, this needs to be folded into the regular
# option tests.

test optionsyntax-1.1 {local option names are saved} {
    cleanup

    type dog {
        option -foo
        option -bar
    }

    set ::dog::Snit_optionInfo(local)
} {-foo -bar}

test optionsyntax-1.2 {islocal flag is set} {
    cleanup

    type dog {
        option -foo
    }

    set ::dog::Snit_optionInfo(islocal--foo)
} {1}

test optionsyntax-2.1 {implicit resource and class} {
    cleanup

    type dog {
        option -foo
    }

    list \
        $::dog::Snit_optionInfo(resource--foo) \
        $::dog::Snit_optionInfo(class--foo)
} {foo Foo}

test optionsyntax-2.2 {explicit resource, default class} {
    cleanup

    type dog {
        option {-foo ffoo}
    }

    list \
        $::dog::Snit_optionInfo(resource--foo) \
        $::dog::Snit_optionInfo(class--foo)
} {ffoo Ffoo}

test optionsyntax-2.3 {explicit resource and class} {
    cleanup

    type dog {
        option {-foo ffoo FFoo}
    }

    list \
        $::dog::Snit_optionInfo(resource--foo) \
        $::dog::Snit_optionInfo(class--foo)
} {ffoo FFoo}

test optionsyntax-2.4 {can't redefine explicit resource} {
    cleanup

    catch {
        type dog {
            option {-foo ffoo}
            option {-foo foo}
        }
    } result

    set result
} {Error in "option {-foo foo}...", resource name redefined from "ffoo" to "foo"}

test optionsyntax-2.5 {can't redefine explicit class} {
    cleanup

    catch {
        type dog {
            option {-foo ffoo Ffoo}
            option {-foo ffoo FFoo}
        }
    } result

    set result
} {Error in "option {-foo ffoo FFoo}...", class name redefined from "Ffoo" to "FFoo"}

test optionsyntax-2.6 {can redefine implicit resource and class} {
    cleanup

    type dog {
        option -foo
        option {-foo ffoo}
        option {-foo ffoo FFoo}
        option -foo
    }
} {::dog}

test optionsyntax-3.1 {no default value} {
    cleanup

    type dog {
        option -foo
    }

    set ::dog::Snit_optionInfo(default--foo)
} {}

test optionsyntax-3.2 {default value, old syntax} {
    cleanup

    type dog {
        option -foo bar
    }

    set ::dog::Snit_optionInfo(default--foo)
} {bar}

test optionsyntax-3.3 {option definition options can be set} {
    cleanup

    type dog {
        option -foo \
            -default Bar \
            -validatemethod Validate \
            -configuremethod Configure \
            -cgetmethod Cget \
            -readonly 1
    }

    list \
        $::dog::Snit_optionInfo(default--foo) \
        $::dog::Snit_optionInfo(validate--foo) \
        $::dog::Snit_optionInfo(configure--foo) \
        $::dog::Snit_optionInfo(cget--foo) \
        $::dog::Snit_optionInfo(readonly--foo)
} {Bar Validate Configure Cget 1}

test optionsyntax-3.4 {option definition option values accumulate} {
    cleanup

    type dog {
        option -foo -default Bar
        option -foo -validatemethod Validate
        option -foo -configuremethod Configure
        option -foo -cgetmethod Cget
        option -foo -readonly 1
    }

    list \
        $::dog::Snit_optionInfo(default--foo) \
        $::dog::Snit_optionInfo(validate--foo) \
        $::dog::Snit_optionInfo(configure--foo) \
        $::dog::Snit_optionInfo(cget--foo) \
        $::dog::Snit_optionInfo(readonly--foo)
} {Bar Validate Configure Cget 1}

test optionsyntax-3.5 {option definition option values can be redefined} {
    cleanup

    type dog {
        option -foo -default Bar
        option -foo -validatemethod Validate
        option -foo -configuremethod Configure
        option -foo -cgetmethod Cget
        option -foo -readonly 1
        option -foo -default Bar2
        option -foo -validatemethod Validate2
        option -foo -configuremethod Configure2
        option -foo -cgetmethod Cget2
        option -foo -readonly 0
    }

    list \
        $::dog::Snit_optionInfo(default--foo) \
        $::dog::Snit_optionInfo(validate--foo) \
        $::dog::Snit_optionInfo(configure--foo) \
        $::dog::Snit_optionInfo(cget--foo) \
        $::dog::Snit_optionInfo(readonly--foo)
} {Bar2 Validate2 Configure2 Cget2 0}

test optionsyntax-3.6 {option -readonly defaults to 0} {
    cleanup

    type dog {
        option -foo
    }

    set ::dog::Snit_optionInfo(readonly--foo)
} {0}

test optionsyntax-3.7 {option -readonly can be any boolean} {
    cleanup

    type dog {
        option -foo -readonly 0
        option -foo -readonly 1
        option -foo -readonly y
        option -foo -readonly n
    }
} {::dog}

test optionsyntax-3.8 {option -readonly must be a boolean} {
    cleanup

    catch {
        type dog {
            option -foo -readonly foo
        }
    } result

    set result
} {Error in "option -foo...", -readonly requires a boolean, got "foo"}

test optionsyntax-3.9 {option -readonly can't be empty} {
    cleanup

    catch {
        type dog {
            option -foo -readonly {}
        }
    } result

    set result
} {Error in "option -foo...", -readonly requires a boolean, got ""}

#-----------------------------------------------------------------------
# 'delegate option' Syntax tests.
#
# This set of tests verifies that the 'delegation option' statement is 
# interpreted properly, and that the type's optionInfo
# array is initialized properly.
#
# TBD: At some point, this needs to be folded into the regular
# option tests.

test delegateoptionsyntax-1.1 {'delegated' lists delegated option names} {
    cleanup

    type dog {
        delegate option -foo to comp
        delegate option -bar to comp
    }

    set ::dog::Snit_optionInfo(delegated)
} {-foo -bar}

test delegateoptionsyntax-1.2 {'delegated' does not include '*'} {
    cleanup

    type dog {
        delegate option * to comp
    }

    set ::dog::Snit_optionInfo(delegated)
} {}

test delegateoptionsyntax-1.3 {'islocal' is set to 0} {
    cleanup

    type dog {
        delegate option -foo to comp
    }

    set ::dog::Snit_optionInfo(islocal--foo)
} {0}

test delegateoptionsyntax-1.4 {'islocal' is not set for '*'} {
    cleanup

    type dog {
        delegate option * to comp
    }

    info exists ::dog::Snit_optionInfo(islocal-*)
} {0}

test delegateoptionsyntax-1.5 {'delegated-$comp' lists options for the component} {
    cleanup

    type dog {
        delegate option -foo to comp1
        delegate option -bar to comp1
        delegate option -baz to comp2

        # The * won't show up.
        delegate option * to comp2
    }

    list \
        $::dog::Snit_optionInfo(delegated-comp1) \
        $::dog::Snit_optionInfo(delegated-comp2)
} {{-foo -bar} -baz}

test delegateoptionsyntax-1.6 {'except' is empty by default} {
    cleanup

    type dog {
        delegate option -foo to comp
    }

    set ::dog::Snit_optionInfo(except)
} {}

test delegateoptionsyntax-1.7 {'except' lists exceptions} {
    cleanup

    type dog {
        delegate option * to comp except {-foo -bar -baz}
    }

    set ::dog::Snit_optionInfo(except)
} {-foo -bar -baz}

test delegateoptionsyntax-1.8 {'target-$opt' set with default} {
    cleanup

    type dog {
        delegate option -foo to comp
    }

    set ::dog::Snit_optionInfo(target--foo)
} {comp -foo}

test delegateoptionsyntax-1.9 {'target-$opt' set explicitly} {
    cleanup

    type dog {
        delegate option -foo to comp as -bar
    }

    set ::dog::Snit_optionInfo(target--foo)
} {comp -bar}

test delegateoptionsyntax-1.10 {'starcomp' is {} by default} {
    cleanup

    type dog {
        delegate option -foo to comp
    }

    set ::dog::Snit_optionInfo(starcomp)
} {}

test delegateoptionsyntax-1.11 {'starcomp' set for *} {
    cleanup

    type dog {
        delegate option * to comp
    }

    set ::dog::Snit_optionInfo(starcomp)
} {comp}

test delegatedoptionsyntax-2.1 {implicit resource and class} {
    cleanup

    type dog {
        delegate option -foo to comp
    }

    list \
        $::dog::Snit_optionInfo(resource--foo) \
        $::dog::Snit_optionInfo(class--foo)
} {foo Foo}

test optionsyntax-2.2 {explicit resource, default class} {
    cleanup

    type dog {
        delegate option {-foo ffoo} to comp
    }

    list \
        $::dog::Snit_optionInfo(resource--foo) \
        $::dog::Snit_optionInfo(class--foo)
} {ffoo Ffoo}

test optionsyntax-2.3 {explicit resource and class} {
    cleanup

    type dog {
        delegate option {-foo ffoo FFoo} to comp
    }

    list \
        $::dog::Snit_optionInfo(resource--foo) \
        $::dog::Snit_optionInfo(class--foo)
} {ffoo FFoo}

test optionsyntax-2.4 {* doesn't get resource and class} {
    cleanup

    type dog {
        delegate option * to comp
    }

    list \
        [info exist ::dog::Snit_optionInfo(resource-*)] \
        [info exist ::dog::Snit_optionInfo(class-*)]
} {0 0}

#-----------------------------------------------------------------------
# Cget cache

test cgetcache-1.1 {Instance rename invalidates cache} {
    cleanup

    type dog {
        option -foo -default bar -cgetmethod getfoo

        method getfoo {option} {
            return $options($option)
        }
    }

    dog fido -foo quux

    # Cache the cget command.
    fido cget -foo
        
    rename fido spot
        
    spot cget -foo
} {quux}

test cgetcache-1.2 {Component rename invalidates cache} {
    cleanup

    type tail {
        option -foo bar
    }

    type dog {
        delegate option -foo to tail

        constructor {args} {
            set tail [tail %AUTO%]
            $tail configure -foo quux
        }

        method retail {} {
            set tail [tail %AUTO%]
        }
    }

    dog fido

    # Cache the cget command.
    fido cget -foo

    # Invalidate the cache
    fido retail

    fido cget -foo
} {bar}

test cgetcache-1.3 {Invalid -cgetmethod causes error} {
    cleanup

    type dog {
        option -foo -default bar -cgetmethod bogus
    }

    dog fido -foo quux

    catch {fido cget -foo} result
    set result
} {can't cget -foo, "::fido bogus" is not defined}

test cgetcache-1.3 {hierarchical -cgetmethod} {
    cleanup

    type dog {
        option -foo -default bar -cgetmethod {Get Opt}

        method {Get Opt} {option} {
            return Dummy
        }
    }

    dog fido

    fido cget -foo
} {Dummy}



#-----------------------------------------------------------------------
# Configure cache

test configurecache-1.1 {Instance rename invalidates cache} {
    cleanup

    type dog {
        option -foo -default bar -configuremethod setfoo

        method setfoo {option value} {
            $self setoption $option $value
        }

        method setoption {option value} {
            set options($option) $value
        }
    }

    # Set the option on creation; this will cache the 
    # configure command.
    dog fido -foo quux

    rename fido spot
    
    spot configure -foo baz
    spot cget -foo
} {baz}

test configurecache-1.2 {Component rename invalidates cache} {
    cleanup

    type tail {
        option -foo bar
    }

    type dog {
        delegate option -foo to tail

        constructor {args} {
            set tail [tail thistail]
            $self configurelist $args
        }

        method retail {} {
            # Give it a new component
            set tail [tail thattail]
        }
    }

    # Set the tail's -foo, and cache the command.
    dog fido -foo quux

    # Invalidate the cache
    fido retail

    # Should recache, and set the new tail's option.
    fido configure -foo baz

    fido cget -foo
} {baz}

test configurecache-1.3 {Invalid -configuremethod causes error} {
    cleanup

    type dog {
        option -foo -default bar -configuremethod bogus
    }

    dog fido
    catch {fido configure -foo quux} result
    set result
} {can't configure -foo, "::fido bogus" is not defined}

test configurecache-1.4 {hierarchical -configuremethod} {
    cleanup

    type dog {
        option -foo -default bar -configuremethod {Set Opt}

        method {Set Opt} {option value} {
            set options($option) Dummy
        }
    }

    dog fido -foo NotDummy
    fido cget -foo
} {Dummy}



#-----------------------------------------------------------------------
# option -validatemethod

test validatemethod-1.1 {Validate method is called} {
    cleanup

    type dog {
        variable flag 0

        option -color \
            -default black \
            -validatemethod ValidateColor

        method ValidateColor {option value} {
            set flag 1
        }

        method getflag {} {
            return $flag
        }
    }

    dog fido -color brown
    fido getflag
} {1} 

test validatemethod-1.2 {Validate method gets correct arguments} {
    cleanup

    type dog {
        option -color \
            -default black \
            -validatemethod ValidateColor

        method ValidateColor {option value} {
            if {$option ne "-color" ||
                $value ne "brown"} {
                error "Expected '-color brown'"
            }
        }
    }

    dog fido -color brown
} {::fido} 

test validatemethod-1.3 {Invalid -validatemethod causes error} {
    cleanup

    type dog {
        option -foo -default bar -validatemethod bogus
    }

    dog fido
    catch {fido configure -foo quux} result
    set result
} {can't validate -foo, "::fido bogus" is not defined}

test validatemethod-1.4 {hierarchical -validatemethod} {
    cleanup

    type dog {
        option -foo -default bar -validatemethod {Val Opt}

        method {Val Opt} {option value} {
            error "Dummy"
        }
    }

    catch {dog fido -foo value} result
    
    set result
} {Error in constructor: Dummy}



#-----------------------------------------------------------------------
# option -readonly semantics

test optionreadonly-1.1 {Readonly options can be set at creation time} {
    cleanup

    type dog {
        option -color \
            -default black \
            -readonly true
    }

    dog fido -color brown

    fido cget -color
} {brown} 

test optionreadonly-1.2 {Readonly options can't be set after creation} {
    cleanup

    type dog {
        option -color \
            -default black \
            -readonly true
    }

    dog fido

    catch {
        fido configure -color brown
    } result

    set result
} {option -color can only be set at instance creation} 

test optionreadonly-1.3 {Readonly options can't be set after creation} {
    cleanup

    type dog {
        option -color \
            -default black \
            -readonly true
    }

    dog fido -color yellow

    catch {
        fido configure -color brown
    } result

    set result
} {option -color can only be set at instance creation} 

#-----------------------------------------------------------------------
# Pragma -hastypeinfo

test hastypeinfo-1.1 {$type info is defined by default} {
    cleanup

    type dog {
        typevariable foo
    }

    dog info typevars
} {::dog::foo} 

test hastypeinfo-1.2 {$type info can be disabled} {
    cleanup

    type dog {
        pragma -hastypeinfo no
        typevariable foo
    }

    catch {
        dog info typevars
    } result
    set result
} {"::dog info" is not defined} 


#-----------------------------------------------------------------------
# Pragma -hastypedestroy

test hastypedestroy-1.1 {$type destroy is defined by default} {
    cleanup

    type dog { 
        typevariable foo
    }

    dog destroy

    catch {
        ::dog info typevars
    } result

    set result
} {invalid command name "::dog"} 

test hastypedestroy-1.2 {$type destroy can be disabled} {
    cleanup

    type dog {
        pragma -hastypedestroy no
        typevariable foo
    }

    catch {
        dog destroy
    } result

    # If we don't delete the type explicitly, "cleanup" will 
    # fail in the next test.
    namespace delete ::dog
    rename ::dog ""

    set result
} {"::dog destroy" is not defined} 

#-----------------------------------------------------------------------
# Pragma -hasinstances

test hasinstances-1.1 {-hasinstances is true by default} {
    cleanup

    catch {
        type dog { 
            method bark {} {
                return "Woof"
            }
        }
        
        dog fido
        fido bark
    } result

    set result
} {Woof} 

test hasinstances-1.2 {'-hasinstances no' disables explicit object creation} {
    cleanup

    type dog { 
        pragma -hasinstances no
    }

    catch {
        dog create fido
    } result
    set result
} {"::dog create" is not defined} 

test hasinstances-1.3 {'-hasinstances no' disables implicit object creation} {
    cleanup

    type dog { 
        pragma -hasinstances no
    }

    catch {
        dog fido
    } result
    set result
} {"::dog fido" is not defined} 

#-----------------------------------------------------------------------
# pragma -canreplace

test canreplace-1.1 {By default, "-canreplace no"} {
    cleanup

    type dog { 
        # ...
    }

    catch {
        dog fido
        dog fido
    } result
    set result
} {command "::fido" already exists} 

test canreplace-1.2 {Can replace commands when "-canreplace yes"} {
    cleanup

    type dog { 
        pragma -canreplace yes
    }

    dog fido
    dog fido
} {::fido} 

#-----------------------------------------------------------------------
# pragma -hasinfo

test hasinfo-1.1 {$obj info is defined by default} {
    cleanup

    type dog {
        variable foo
    }

    dog spot
    spot info vars
} {::dog::Snit_inst1::foo} 

test hasinfo-1.2 {$obj info can be disabled} {
    cleanup

    type dog {
        pragma -hasinfo no
        variable foo
    }

    catch {
        dog spot
        spot info vars
    } result
    set result
} {"::spot info" is not defined} 

#-----------------------------------------------------------------------
# pragma -hastypemethods
#
# The "-hastypemethods yes" case is tested by the bulk of this file.
# We'll test the "-hastypemethods no" case here.

test hastypemethods-1.1 {-hastypemethods no, $type foo creates instance.} {
    cleanup

    type dog {
        pragma -hastypemethods no
        variable foo
    }

    set res [dog spot]

    # Create a new type dog that has a destroy typemethod, so the
    # cleanup works.
    type dog {
        variable foo
    }

    set res
} {::spot} 

test hastypemethods-1.2 {-hastypemethods no, $type create foo fails.} {
    catch {cleanup}

    type dog {
        pragma -hastypemethods no
        variable foo
    }

    catch {dog create spot} result

    # Create a new type dog that has a destroy typemethod, so the
    # cleanup works.
    type dog {
        variable foo
    }

    set result
} {Error in constructor: wrong # args: should be "::dog::Snit_constructor type selfns win self"} 

test hastypemethods-1.3 {-hastypemethods no, $type info fails.} {
    catch {cleanup}

    type dog {
        pragma -hastypemethods no
        variable foo
    }

    catch {dog info} result

    # Create a new type dog that has a destroy typemethod, so the
    # cleanup works.
    type dog {
        variable foo
    }

    set result
} {command "::info" already exists} 

test hastypemethods-1.4 {-hastypemethods no, [$widget] fails.} tk {
    catch {cleanup}

    widget dog {
        pragma -hastypemethods no
        variable foo
    }

    catch {dog} result

    # Create a new type dog that has a destroy typemethod, so the
    # cleanup works.
    widget dog {
        variable foo
    }

    set result
} {wrong # args: should be "::dog name args"} 

test hastypemethods-1.5 {-hastypemethods no, -hasinstances no fails.} {
    catch {cleanup}

    catch {
        type dog {
            pragma -hastypemethods no
            pragma -hasinstances no
            variable foo
        }
    } result

    set result
} {type ::dog has neither typemethods nor instances} 

#-----------------------------------------------------------------------
# -simpledispatch yes

test simpledispatch-1.1 {not allowed with method delegation.} {
    catch {cleanup}

    catch {
        type dog {
            pragma -simpledispatch yes

            delegate method foo to bar
        }
    } result

    set result
} {type ::dog requests -simpledispatch but delegates methods.} 

test simpledispatch-1.2 {normal methods work with simpledispatch.} {
    catch {cleanup}

    type dog {
        pragma -simpledispatch yes
        
        method barks {how} {
            return "$self barks $how."
        }
    }
    
    dog spot
    spot barks loudly
} {::spot barks loudly.} 

test simpledispatch-1.3 {option methods work with simpledispatch.} {
    catch {cleanup}

    type dog {
        pragma -simpledispatch yes
        
        option -breed mutt
    }
    
    dog spot
    set a [spot cget -breed]
    spot configure -breed collie
    set b [spot cget -breed]
    spot configurelist [list -breed sheltie]
    set c [spot cget -breed]

    list $a $b $c
} {mutt collie sheltie} 

test simpledispatch-1.4 {info method works with simpledispatch.} {
    catch {cleanup}

    type dog {
        pragma -simpledispatch yes
        
        option -breed mutt
    }
    
    dog spot
    
    spot info options
} {-breed} 

test simpledispatch-1.5 {destroy method works with simpledispatch.} {
    catch {cleanup}

    type dog {
        pragma -simpledispatch yes
        
        option -breed mutt
    }
    
    dog spot
    set a [info commands ::spot]
    spot destroy
    set b [info commands ::spot]
    list $a $b
} {::spot {}} 

test simpledispatch-1.6 {no hierarchical methods with simpledispatch.} {
    catch {cleanup}

    catch {
        type dog {
            pragma -simpledispatch yes
            
            method {wag tail} {} {}
        }
    } result

    set result
} {type ::dog requests -simpledispatch but defines hierarchical methods.} 

#-----------------------------------------------------------------------
# Exotic return codes

test break-1.1 {Methods can "return -code break"} {
    cleanup
    
    snit::type dog {
        method bark {} {return -code break "Breaking"}
    }

    dog spot

    catch {spot bark} result
} {3}

test break-1.2 {Typemethods can "return -code break"} {
    cleanup
    
    snit::type dog {
        typemethod bark {} {return -code break "Breaking"}
    }

    catch {dog bark} result
} {3}

test break-1.3 {Methods called via mymethod "return -code break"} {
    cleanup
    
    snit::type dog {
        method bark {} {return -code break "Breaking"}

        method getbark {} {
            return [mymethod bark]
        }
    }

    dog spot

    catch {uplevel \#0 [spot getbark]} result
} {3}

#-----------------------------------------------------------------------
# Bug Fixes

test bug-1.1 {Bug 1161779: destructor can't precede constructor} \
    -cleanup {rename ::dummy ""} \
    -body {
        type dummy {
            destructor {
                # No content
            }

            constructor {args} {
                $self configurelist $args
            }
            
        }
    } -result ::dummy

test bug-2.1 {Bug 1106375: Widget Error on failed object's construction} \
    -cleanup {::mylabel destroy} \
    -body {
        ::snit::widgetadaptor mylabel {
            delegate method * to hull
            delegate option * to hull 
            
            constructor {args} {
                installhull using label 
                error "simulated error"
            }
        }
        
        catch {mylabel .lab} result
        list [info commands .lab] $result
 
    } -result {{} {Error in constructor: simulated error}}

test bug-2.2 {Bug 1106375: Widget Error on failed object's construction} \
    -cleanup {::myframe destroy} \
    -body {
        ::snit::widget myframe {
            delegate method * to hull
            delegate option * to hull 
            
            constructor {args} {
                error "simulated error"
            }
        }
        
        catch {myframe .frm} result
        list [info commands .frm] $result
 
    } -result {{} {Error in constructor: simulated error}}



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

::tcltest::cleanupTests
