require File.expand_path(File.dirname(__FILE__) + '/test_helper')
require File.expand_path(File.dirname(__FILE__) + '/data/models')

# Higher level tests.

class ActiveRecordTest < ActiveSupport::TestCase
  def setup
    I18n.locale = :en
    reset_db!
    ActiveRecord::Base.locale = nil
  end

  def assert_translated(locale, record, names, expected)
    I18n.locale = locale
    assert_equal Array(expected), Array(names).map { |name| record.send(name) }
  end

  test "a translated record has translations" do
    assert_equal [], Post.new.translations
  end

  test "saves a translated version of the record for each locale" do
    post = Post.create(:subject => 'title')
    I18n.locale = :de
    post.update_attributes(:subject => 'Titel')

    assert_equal 2, post.translations.size
    assert_equal %w(de en), post.translations.map(&:locale).map(&:to_s).sort
    assert_equal %w(Titel title), post.translations.map(&:subject).sort
  end

  test "a translated record has German translations" do
    I18n.locale = :de
    post = Post.create(:subject => 'foo')
    assert_equal 1, post.translations.size
    assert_equal [:de], post.translations.map { |t| t.locale }
  end

  test "modifiying translated fields while switching locales" do
    post = Post.create(:subject => 'title', :content => 'content')
    assert_equal %w(title content), [post.subject, post.content]

    I18n.locale = :de
    post.subject, post.content = 'Titel', 'Inhalt'

    assert_translated(:de, post, [:subject, :content], %w(Titel Inhalt))
    assert_translated(:en, post, [:subject, :content], %w(title content))
    assert_translated(:de, post, [:subject, :content], %w(Titel Inhalt))

    post.save
    post.reload

    assert_translated(:en, post, [:subject, :content], %w(title content))
    assert_translated(:de, post, [:subject, :content], %w(Titel Inhalt))
  end

  test "attribute writers do return their argument" do
    value = Post.new.subject = 'foo'
    assert_equal 'foo', value
  end

  test "update_attribute succeeds with valid values" do
    post = Post.create(:subject => 'foo', :content => 'bar')
    post.update_attribute(:subject, 'baz')
    assert_equal 'baz', Post.first.subject
  end

  test "update_attributes fails with invalid values" do
    post = Post.create(:subject => 'foo', :content => 'bar')
    assert !post.update_attributes(:subject => '')
    assert_not_nil post.reload.attributes['subject']
    assert_equal 'foo', post.subject
  end

  test "passing the locale to create uses the given locale" do
    post = Post.create(:subject => 'Titel', :content => 'Inhalt', :locale => :de)
    assert_equal :en, I18n.locale
    assert_nil ActiveRecord::Base.locale

    I18n.locale = :de
    assert_equal 'Titel', post.subject
  end

  test "passing the locale to attributes= uses the given locale" do
    post = Post.create(:subject => 'title', :content => 'content')
    post.update_attributes(:subject => 'Titel', :content => 'Inhalt', :locale => :de)
    post.reload

    assert_equal :en, I18n.locale
    assert_nil ActiveRecord::Base.locale

    assert_equal 'title', post.subject
    I18n.locale = :de
    assert_equal 'Titel', post.subject
  end

  test 'reload works' do
    post = Post.create(:subject => 'foo', :content => 'bar')
    post.subject = 'baz'
    post.reload
    assert_equal 'foo', post.subject
  end

  test "returns nil if no translations are found (unsaved record)" do
    post = Post.new(:subject => 'foo')
    assert_equal 'foo', post.subject
    assert_nil post.content
  end

  test "returns nil if no translations are found (saved record)" do
    post = Post.create(:subject => 'foo')
    post.reload
    assert_equal 'foo', post.subject
    assert_nil post.content
  end

  test "finds a German post" do
    post = Post.create(:subject => 'foo (en)', :content => 'bar')
    I18n.locale = :de
    post = Post.first
    post.subject = 'baz (de)'
    post.save
    assert_equal 'baz (de)', Post.first.subject
    I18n.locale = :en
    assert_equal 'foo (en)', Post.first.subject
  end

  test "saves an English post and loads correctly" do
    post = Post.create(:subject => 'foo', :content => 'bar')
    assert post.save
    post = Post.first
    assert_equal 'foo', post.subject
    assert_equal 'bar', post.content
  end

  test "returns the value for the correct locale, after locale switching" do
    post = Post.create(:subject => 'foo')
    I18n.locale = :de
    post.subject = 'bar'
    post.save
    I18n.locale = :en
    post = Post.first
    assert_equal 'foo', post.subject
    I18n.locale = :de
    assert_equal 'bar', post.subject
  end

  test "returns the value for the correct locale, after locale switching, without saving" do
    post = Post.create :subject => 'foo'
    I18n.locale = :de
    post.subject = 'bar'
    I18n.locale = :en
    assert_equal 'foo', post.subject
    I18n.locale = :de
    assert_equal 'bar', post.subject
  end

  test "saves all locales, even after locale switching" do
    post = Post.new :subject => 'foo'
    I18n.locale = :de
    post.subject = 'bar'
    I18n.locale = :he
    post.subject = 'baz'
    post.save
    I18n.locale = :en
    post = Post.first
    assert_equal 'foo', post.subject
    I18n.locale = :de
    assert_equal 'bar', post.subject
    I18n.locale = :he
    assert_equal 'baz', post.subject
  end

  test "works with associations" do
    blog = Blog.create
    post1 = blog.posts.create(:subject => 'foo')

    I18n.locale = :de
    post2 = blog.posts.create(:subject => 'bar')
    assert_equal 2, blog.posts.size

    I18n.locale = :en
    assert_equal 'foo', blog.posts.first.subject
    assert_nil blog.posts.last.subject

    I18n.locale = :de
    assert_equal 'bar', blog.posts.last.subject
  end

  test "works with simple dynamic finders" do
    foo = Post.create(:subject => 'foo')
    Post.create(:subject => 'bar')
    post = Post.find_by_subject('foo')
    assert_equal foo, post
  end

  test 'change attribute on globalized model' do
    post = Post.create(:subject => 'foo', :content => 'bar')
    assert_equal [], post.changed
    post.subject = 'baz'
    assert_equal ['subject'], post.changed
    post.content = 'quux'
    assert_member 'subject', post.changed
    assert_member 'content', post.changed
  end

  test 'change attribute on globalized model after locale switching' do
    post = Post.create(:subject => 'foo', :content => 'bar')
    assert_equal [], post.changed
    post.subject = 'baz'
    I18n.locale = :de
    assert_equal ['subject'], post.changed
  end

  test 'complex writing and stashing' do
    post = Post.create(:subject => 'foo', :content => 'bar')
    post.subject = nil
    assert_nil post.subject
    assert !post.valid?
    post.subject = 'stashed_foo'
    assert_equal 'stashed_foo', post.subject
  end

  test 'translated class locale setting' do
    assert ActiveRecord::Base.respond_to?(:locale)
    assert_equal :en, I18n.locale
    assert_nil ActiveRecord::Base.locale

    I18n.locale = :de
    assert_equal :de, I18n.locale
    assert_nil ActiveRecord::Base.locale

    ActiveRecord::Base.locale = :es
    assert_equal :de, I18n.locale
    assert_equal :es, ActiveRecord::Base.locale

    I18n.locale = :fr
    assert_equal :fr, I18n.locale
    assert_equal :es, ActiveRecord::Base.locale
  end

  test "untranslated class responds to locale" do
    assert Blog.respond_to?(:locale)
  end

  test "to ensure locales in different classes are the same" do
    ActiveRecord::Base.locale = :de
    assert_equal :de, ActiveRecord::Base.locale
    assert_equal :de, Parent.locale

    Parent.locale = :es
    assert_equal :es, ActiveRecord::Base.locale
    assert_equal :es, Parent.locale
  end

  test "attribute saving goes by content locale and not global locale" do
    ActiveRecord::Base.locale = :de
    assert_equal :en, I18n.locale
    Post.create :subject => 'foo'
    assert_equal :de, Post.first.translations.first.locale
  end

  test "attribute loading goes by content locale and not global locale" do
    post = Post.create(:subject => 'foo')
    assert_nil ActiveRecord::Base.locale

    ActiveRecord::Base.locale = :de
    assert_equal :en, I18n.locale
    post.update_attribute(:subject, 'foo [de]')
    assert_equal 'foo [de]', Post.first.subject

    ActiveRecord::Base.locale = :en
    assert_equal 'foo', Post.first.subject
  end

  test "access content locale before setting" do
    Globalize::ActiveRecord::ActMacro.class_eval "remove_class_variable(:@@locale)"
    assert_nothing_raised { ActiveRecord::Base.locale }
  end

  test "available_locales" do
    Post.locale = :de
    post = Post.create(:subject => 'foo')
    Post.locale = :es
    post.update_attribute(:subject, 'bar')
    Post.locale = :fr
    post.update_attribute(:subject, 'baz')
    assert_equal [:de, :es, :fr], post.available_locales
    assert_equal [:de, :es, :fr], Post.first.available_locales
  end

  test "saving record correctly after post-save reload" do
    reloader = Reloader.create(:content => 'foo')
    assert_equal 'foo', reloader.content
  end

  test "including translations" do
    I18n.locale = :de
    Post.create(:subject => "Foo1", :content => "Bar1")
    Post.create(:subject => "Foo2", :content => "Bar2")

    class << Post
      def translations_included
        self.all(:include => :translations)
      end
    end

    default       = Post.all.map { |x| [x.subject, x.content] }
    with_include  = Post.translations_included.map { |x| [x.subject, x.content] }
    assert_equal default, with_include
  end

  test "setting multiple translations at once with options hash" do
    Post.locale = :de
    post = Post.create(:subject => "foo1", :content => "foo1")
    Post.locale = :en
    post.update_attributes(:subject => "bar1", :content => "bar1")

    options = { :de => {:subject => "foo2", :content => "foo2"},
                :en => {:subject => "bar2", :content => "bar2"} }
    post.set_translations options
    post.reload

    assert ["bar2", "bar2"], [post.subject, post.content]
    Post.locale = :de
    assert ["foo2", "foo2"], [post.subject, post.content]
  end

  test "setting only one translation with set_translations" do
    Post.locale = :de
    post = Post.create(:subject => "foo1", :content => "foo1")
    Post.locale = :en
    post.update_attributes(:subject => "bar1", :content => "bar1")

    options = { :en => { :subject => "bar2", :content => "bar2" } }
    post.set_translations options
    post.reload

    assert ["bar2", "bar2"], [post.subject, post.content]
    Post.locale = :de
    assert ["foo1", "foo1"], [post.subject, post.content]
  end

  test "setting only selected attributes with set_translations" do
    Post.locale = :de
    post = Post.create(:subject => "foo1", :content => "foo1")
    Post.locale = :en
    post.update_attributes(:subject => "bar1", :content => "bar1")

    options = { :de => { :content => "foo2" }, :en => { :subject => "bar2" } }
    post.set_translations options
    post.reload

    assert ["bar2", "bar1"], [post.subject, post.content]
    Post.locale = :de
    assert ["foo1", "foo2"], [post.subject, post.content]
  end

  test "setting invalid attributes raises ArgumentError" do
    Post.locale = :de
    post = Post.create(:subject => "foo1", :content => "foo1")
    Post.locale = :en
    post.update_attributes(:subject => "bar1", :content => "bar1")

    options = { :de => {:fake => "foo2"} }
    exception = assert_raise(ActiveRecord::UnknownAttributeError) do
      post.set_translations options
    end
    assert_equal "unknown attribute: fake", exception.message
  end

  test "reload accepting find options" do
    p = Post.create(:subject => "Foo", :content => "Bar")
    assert p.reload(:readonly => true, :lock => true)
    assert_raise(ArgumentError) { p.reload(:foo => :bar) }
  end

  test "dependent destroy of translation" do
    p = Post.create(:subject => "Foo", :content => "Bar")
    assert_equal 1, PostTranslation.count
    p.destroy
    assert_equal 0, PostTranslation.count
  end

  test "translating subclass of untranslated comment model" do
    translated_comment = TranslatedComment.create(:post => @post)
    assert_nothing_raised { translated_comment.translations }
  end

  test "modifiying translated comments works as expected" do
    I18n.locale = :en
    translated_comment = TranslatedComment.create(:post => @post, :content => 'foo')
    assert_equal 'foo', translated_comment.content

    I18n.locale = :de
    translated_comment.content = 'bar'
    assert translated_comment.save
    assert_equal 'bar', translated_comment.content

    I18n.locale = :en
    assert_equal 'foo', translated_comment.content

    assert_equal 2, translated_comment.translations.size
  end

  test "can create a proxy class for a namespaced model" do
    assert_nothing_raised do
      module Foo
        module Bar
          class Baz < ActiveRecord::Base
            translates :bumm
          end
        end
      end
    end
  end

  test "attribute translated before type cast" do
    Post.locale = :en
    post = Post.create(:subject => 'foo', :content => 'bar')
    Post.locale = :de
    post.update_attribute(:subject, "German foo")
    assert_equal 'German foo', post.subject_before_type_cast
    Post.locale = :en
    assert_equal 'foo', post.subject_before_type_cast
  end

  test "don't override existing translation class" do
    assert PostTranslation.new.respond_to?(:existing_method)
  end
  
  test "has_many and named scopes work with globalize" do
    blog = Blog.create
    assert_nothing_raised { blog.posts.foobar }
  end

  test "required_attribuets don't include non-translated attributes" do
    validations = [
      stub(:name => :name, :macro => :validates_presence_of),
      stub(:name => :email, :macro => :validates_presence_of)
    ]
    User.expects(:reflect_on_all_validations => validations)
    assert_equal [:name], User.required_attributes
  end
  
  test "attribute_names returns translated and regular attribute names" do
    Post.create :subject => "foo", :content => "bar"
    assert_equal Post.last.attribute_names.sort, %w[blog_id content id subject]
  end
  
  test "attributes returns translated and regular attributes" do
    Post.create :subject => "foo", :content => "bar"
    assert_equal Post.last.attributes.keys.sort, %w[blog_id content id subject]
  end
  
  test "to_xml includes translated fields" do
    Post.create :subject => "foo", :content => "bar"
    assert Post.last.to_xml =~ /subject/
    assert Post.last.to_xml =~ /content/
  end
end

# TODO error checking for fields that exist in main table, don't exist in
# proxy table, aren't strings or text
#
# TODO allow finding by translated attributes in conditions?
# TODO generate advanced dynamic finders?
