### ----------------------------------------------------
### -- AttrX::Mooish
### -- Licenses: Artistic-2.0
### -- Authors: Vadim Belman
### -- File: t/013-validations.rakutest
### ----------------------------------------------------
use Test;
use AttrX::Mooish;
use AttrX::Mooish::X;

#?mooish-custom

plan 5;

subtest "Immutable" => {
    plan 2;

    my class Foo1 {
        has $.bar is rw is mooish(:lazy);
        has $.baz is mooish(:lazy);

        method build-bar { "is bar" }
        method build-baz { "is baz" }
    }

    my $inst = Foo1.new;
    lives-ok { $inst.bar = "Fine" }, "assignment to RW attribute";
    throws-like
        { $inst.baz = "Bad"; },
        X::Assignment::RO,
        message => 'Cannot modify an immutable Str (is baz)',
        "assignment to RO attribute failes";
}

subtest "Type check" => {
    plan 3;

    my class Foo2 {
        has Int $.bar is rw is mooish(:lazy);
        has $.baz is mooish(:lazy);

        method build-bar { 1234 }
        method build-baz { "is baz" }
    }

    my $inst = Foo2.new;
    lives-ok { $inst.bar = 31415926 }, "assignment of same types";
    lives-ok { $inst.bar = Nil }, "assignment of Nil";
    throws-like { $inst.bar = "abc" },
        X::TypeCheck::Assignment,
        "assignment to a different attribute type";
}

subtest "Nil to a definite smiley" => {
    plan 2;

    my class Foo3 {
        has Stringy $.s is mooish(:lazy) = Str;
        has Str:D $.bar is rw is mooish(:trigger) = "";

        method trigger-bar($) { }
    }

    my $inst = Foo3.new;
    lives-ok { $inst.bar = "a string" }, "assignment of defined value";
    throws-like
        { $inst.bar = Nil },
        AttrX::Mooish::X::StoreValue,
        exception => X::TypeCheck,
        "assignment of Nil to a definite type attribute";
}

subtest "Where-clause" => {
    plan 2;

    my class Foo4 {
        has Str $.bar is rw is mooish(:lazy) where * ~~ /:i ^ a/;

        method build-bar {
            "a default value"
        }
    }

    my $inst = Foo4.new;
    lives-ok { $inst.bar = "another value" }, "value assigned matches 'where' constraint";
    throws-like
        { $inst.bar = "not allowed" }, X::TypeCheck,
        message => q{Type check failed in assignment to $!bar; expected <anon> but got Str ("not allowed")},
        "assignment of non-compliant value";
}

# Make sure lazy attributes can be of definite type!
subtest "Definite lazy attribute" => {
    plan 15;

    my class Foo5 {
        has Str:D $.good is rw is mooish(:lazy, :predicate, :clearer);
        has Int:D $.bad is rw is mooish(:lazy);
        has Num:D $!priv is mooish(:lazy, :predicate, :clearer);

        method build-good { "oki" }
        method build-bad { Int }
        method !build-priv { pi }
        method priv { $!priv }
        method has-priv { self!has-priv }
        method clear-priv { self!clear-priv }
    }

    my $inst;
    lives-ok { $inst = Foo5.new }, "can create a class with lazy definite attribute";
    is $inst.good, "oki", "the good attribute is initialized";
    ok $inst.has-good, "the good attribute is marked as set";
    lives-ok { $inst.good = "42" }, "can assign to a writable definite attribute";
    is $inst.good, "42", "the assignment is not lost";
    lives-ok { $inst.clear-good }, "a definite attribute can be cleared";
    nok $inst.has-good, "a definite attribute is empty after clearing";
    is $inst.good, "oki", "a definite attribute re-vivifies after clear";

    is $inst.priv, pi, "private lazy attribute can be a definite too";
    lives-ok { $inst.clear-priv }, "private definite attribute can be cleared";
    nok $inst.has-priv, "private definite is empty after clearing";
    is $inst.priv, pi, "private definite re-vivifies after clearing";

    throws-like
        { my $v = $inst.bad },
        AttrX::Mooish::X::StoreValue,
        exception => X::TypeCheck,
        "cannot initialize a definite lazy attribute with a type object";
    lives-ok { $inst.bad = 12 }, "assignment into a bad definite is still possible";

    throws-like
        q<my class Foo6 { has Str:D $.bad is mooish(:trigger); }>,
        X::Syntax::Variable::MissingInitializer,
        "non-lazy definite attribute has to be initialized";
}

done-testing;
# vim: ft=raku


