Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

First actual import of code from Active Admin

  • Loading branch information...
commit 7834b7a4a79e4945fa31bfe6ae7b8939ccd07668 1 parent cb76420
@gregbell gregbell authored
View
BIN  .DS_Store
Binary file not shown
View
4 Gemfile
@@ -2,3 +2,7 @@ source "http://rubygems.org"
# Specify your gem's dependencies in arbre.gemspec
gemspec
+
+group :test do
+ gem "rspec"
+end
View
35 README.rdoc
@@ -1,3 +1,34 @@
-Arbre - Coming Soon. For now, checkout Active Admin:
+= Arbre - Ruby Object Oriented HTML Views
-http://github.com/gregbell/active_admin
+Arbre is the DOM implemented in Ruby. This project is primarily used as
+the object oriented view layer in Active Admin.
+
+== Simple Usage
+
+A simple example of setting up an Arbre context and creating some html
+
+ html = Arbre::Context.new do
+ h2 "Why Arbre is awesome?"
+
+ ul do
+ li "The DOM is implemented in ruby"
+ li "You can create object oriented views"
+ li "Templates suck"
+ end
+ end
+
+ puts html.to_s #=> <h2>Why</h2><ul><li></li></ul>
+
+
+== The DOM in Ruby
+
+The purpose of Arbre is to leave the view as ruby objects as long
+as possible. This allows OO Design to be used to implement the view layer.
+
+
+ html = Arbre::Context.new do
+ h2 "Why Arbre is awesome?"
+ end
+
+ html.children.size #=> 1
+ html.children.first #=> #<Arbre::HTML::H2>
View
2  arbre.gemspec
@@ -16,4 +16,6 @@ Gem::Specification.new do |s|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
s.require_paths = ["lib"]
+
+ s.add_dependency("activesupport", ">= 3.0.0")
end
View
BIN  lib/.DS_Store
Binary file not shown
View
13 lib/arbre.rb
@@ -1,2 +1,15 @@
+require 'active_support/core_ext/string/output_safety'
+require 'active_support/inflector'
+
module Arbre
end
+
+require 'arbre/element'
+require 'arbre/context'
+require 'arbre/html/attributes'
+require 'arbre/html/class_list'
+require 'arbre/html/tag'
+require 'arbre/html/text_node'
+require 'arbre/html/document'
+require 'arbre/html/html5_elements'
+require 'arbre/component'
View
80 lib/arbre/builder.rb
@@ -0,0 +1,80 @@
+module Arbre
+
+ # Include this module in any context to start building html.
+ #
+ # assigns = {}
+ # include Arbre::Builder
+ # span("foo").to_s #=> "<span>foo</span>
+ #
+ # When you include the module, you are required to setup 2 variables:
+ #
+ # * assigns: This is a hash that includes local variables
+ # * helpers: This is an object that provides helper methods to all
+ # objects within the context.
+ module Builder
+
+ module BuilderMethods
+ def build_tag(klass, *args, &block)
+ tag = klass.new(assigns, helpers)
+ tag.parent = current_dom_context
+
+ # If you passed in a block and want the object
+ if block_given? && block.arity > 0
+ # Set out context to the tag, and pass responsibility to the tag
+ with_current_dom_context tag do
+ tag.build(*args, &block)
+ end
+ else
+ # Build the tag
+ tag.build(*args)
+
+ # Render the blocks contents
+ if block_given?
+ with_current_dom_context tag do
+ append_return_block(yield)
+ end
+ end
+ end
+
+ tag
+ end
+
+ def insert_tag(klass, *args, &block)
+ tag = build_tag(klass, *args, &block)
+ current_dom_context.add_child(tag)
+ tag
+ end
+
+ def current_dom_context
+ @__current_dom_element_buffer__ ||= [self]
+ current_element = @__current_dom_element_buffer__.last
+ if current_element == self
+ self
+ else
+ current_element.current_dom_context
+ end
+ end
+
+ def with_current_dom_context(tag)
+ raise ArgumentError, "Can't be in the context of nil. #{@__current_dom_element_buffer__.inspect}" unless tag
+ current_dom_context # Ensure a context is setup
+ @__current_dom_element_buffer__.push tag
+ yield
+ @__current_dom_element_buffer__.pop
+ end
+ alias_method :within, :with_current_dom_context
+
+ # Appends the value to the current DOM element if there are no
+ # existing DOM Children and it responds to #to_s
+ def append_return_block(tag)
+ return nil if current_dom_context.children?
+
+ if !tag.is_a?(Arbre::Element) && tag.respond_to?(:to_s)
+ current_dom_context << Arbre::HTML::TextNode.from_string(tag.to_s)
+ end
+ end
+ end
+
+ end
+end
+
View
22 lib/arbre/component.rb
@@ -0,0 +1,22 @@
+module Arbre
+ class Component < Arbre::HTML::Div
+
+ # By default components render a div
+ def tag_name
+ 'div'
+ end
+
+ def initialize(*)
+ super
+ add_class default_class_name
+ end
+
+ protected
+
+ # By default, add a css class named after the ruby class
+ def default_class_name
+ self.class.name.demodulize.underscore
+ end
+
+ end
+end
View
52 lib/arbre/context.rb
@@ -0,0 +1,52 @@
+require 'arbre/element'
+
+module Arbre
+ class Context < Element
+
+ def initialize(assigns = {}, helpers = nil, &block)
+ assigns.symbolize_keys!
+ super(assigns, helpers)
+ instance_eval &block
+ end
+
+ def indent_level
+ # A context does not increment the indent_level
+ super - 1
+ end
+
+ def bytesize
+ cached_html.bytesize
+ end
+ alias :length :bytesize
+
+ def respond_to?(method)
+ super || cached_html.respond_to?(method)
+ end
+
+ # Webservers treat Arbre::Context as a string. We override
+ # method_missing to delegate to the string representation
+ # of the html.
+ def method_missing(method, *args, &block)
+ if cached_html.respond_to? method
+ cached_html.send method, *args, &block
+ else
+ super
+ end
+ end
+
+ private
+
+ # Caches the rendered HTML so that we don't re-render just to
+ # get the content lenght or to delegate a method to the HTML
+ def cached_html
+ if defined?(@cached_html)
+ @cached_html
+ else
+ html = to_s
+ @cached_html = html if html.length > 0
+ html
+ end
+ end
+
+ end
+end
View
191 lib/arbre/element.rb
@@ -0,0 +1,191 @@
+require 'arbre/builder'
+require 'arbre/element_collection'
+
+module Arbre
+
+ class Element
+ include Builder
+ include Builder::BuilderMethods
+
+ attr_accessor :parent
+ attr_reader :children
+
+ def self.builder_method(method_name)
+ Builder::BuilderMethods.class_eval <<-EOF, __FILE__, __LINE__
+ def #{method_name}(*args, &block)
+ insert_tag ::#{self.name}, *args, &block
+ end
+ EOF
+ end
+
+ def initialize(assigns = {}, helpers = nil)
+ @_assigns, @_helpers = assigns, helpers
+ @children = ElementCollection.new
+ end
+
+ def assigns
+ @_assigns
+ end
+
+ def helpers
+ @_helpers
+ end
+
+ def tag_name
+ @tag_name ||= self.class.name.demodulize.downcase
+ end
+
+ def build(*args, &block)
+ # Render the block passing ourselves in
+ append_return_block(block.call(self)) if block
+ end
+
+ def add_child(child)
+ return unless child
+
+ if child.is_a?(Array)
+ child.each{|item| add_child(item) }
+ return @children
+ end
+
+ # If its not an element, wrap it in a TextNode
+ unless child.is_a?(Element)
+ child = Arbre::HTML::TextNode.from_string(child)
+ end
+
+ if child.respond_to?(:parent)
+ # Remove the child
+ child.parent.remove_child(child) if child.parent
+ # Set ourselves as the parent
+ child.parent = self
+ end
+
+ @children << child
+ end
+
+ def remove_child(child)
+ child.parent = nil if child.respond_to?(:parent=)
+ @children.delete(child)
+ end
+
+ def <<(child)
+ add_child(child)
+ end
+
+ def children?
+ @children.any?
+ end
+
+ def parent=(parent)
+ @parent = parent
+ end
+
+ def parent?
+ !@parent.nil?
+ end
+
+ def ancestors
+ if parent?
+ [parent] + parent.ancestors
+ else
+ []
+ end
+ end
+
+ # TODO: Shouldn't grab whole tree
+ def find_first_ancestor(type)
+ ancestors.find{|a| a.is_a?(type) }
+ end
+
+ def content=(contents)
+ clear_children!
+ add_child(contents)
+ end
+
+ def get_elements_by_tag_name(tag_name)
+ elements = ElementCollection.new
+ children.each do |child|
+ elements << child if child.tag_name == tag_name
+ elements.concat(child.get_elements_by_tag_name(tag_name))
+ end
+ elements
+ end
+ alias_method :find_by_tag, :get_elements_by_tag_name
+
+ def get_elements_by_class_name(class_name)
+ elements = ElementCollection.new
+ children.each do |child|
+ elements << child if child.class_list =~ /#{class_name}/
+ elements.concat(child.get_elements_by_tag_name(tag_name))
+ end
+ elements
+ end
+ alias_method :find_by_class, :get_elements_by_class_name
+
+ def content
+ children.to_s
+ end
+
+ def html_safe
+ to_s
+ end
+
+ def indent_level
+ parent? ? parent.indent_level + 1 : 0
+ end
+
+ def each(&block)
+ [to_s].each(&block)
+ end
+
+ def to_str
+ to_s
+ end
+
+ def to_s
+ content
+ end
+
+ def +(element)
+ case element
+ when Element, ElementCollection
+ else
+ element = Arbre::HTML::TextNode.from_string(element)
+ end
+ ElementCollection.new([self]) + element
+ end
+
+ def to_ary
+ ElementCollection.new [self]
+ end
+ alias_method :to_a, :to_ary
+
+ private
+
+ # Resets the Elements children
+ def clear_children!
+ @children.clear
+ end
+
+ # Implements the method lookup chain. When you call a method that
+ # doesn't exist, we:
+ #
+ # 1. Try to call the method on the current DOM context
+ # 2. Return an assigned variable of the same name
+ # 3. Call the method on the helper object
+ # 4. Call super
+ #
+ def method_missing(name, *args, &block)
+ if current_dom_context.respond_to?(name)
+ current_dom_context.send name, *args, &block
+ elsif assigns && assigns.has_key?(name)
+ assigns[name]
+ elsif helpers.respond_to?(name)
+ helpers.send(name, *args, &block)
+ else
+ super
+ end
+ end
+
+ end
+end
View
25 lib/arbre/element_collection.rb
@@ -0,0 +1,25 @@
+module Arbre
+
+ # Stores a collection of Element objects
+ class ElementCollection < Array
+
+ def +(other)
+ self.class.new(super)
+ end
+
+ def -(other)
+ self.class.new(super)
+ end
+
+ def &(other)
+ self.class.new(super)
+ end
+
+ def to_s
+ self.collect do |element|
+ element.to_s
+ end.join.html_safe
+ end
+ end
+
+end
View
20 lib/arbre/html/attributes.rb
@@ -0,0 +1,20 @@
+module Arbre
+ module HTML
+
+ class Attributes < Hash
+
+ def to_s
+ self.collect do |name, value|
+ "#{html_escape(name)}=\"#{html_escape(value)}\""
+ end.join(" ")
+ end
+
+ protected
+
+ def html_escape(s)
+ ERB::Util.html_escape(s)
+ end
+ end
+
+ end
+end
View
24 lib/arbre/html/class_list.rb
@@ -0,0 +1,24 @@
+require 'set'
+
+module Arbre
+ module HTML
+
+ # Holds a set of classes
+ class ClassList < Set
+
+ def add(class_names)
+ class_names.to_s.split(" ").each do |class_name|
+ super(class_name)
+ end
+ self
+ end
+ alias :<< :add
+
+ def to_s
+ to_a.join(" ")
+ end
+
+ end
+
+ end
+end
View
42 lib/arbre/html/document.rb
@@ -0,0 +1,42 @@
+module Arbre
+ module HTML
+
+ class Document < Tag
+
+ def build(*args)
+ super
+ build_head
+ build_body
+ end
+
+ def document
+ self
+ end
+
+ def tag_name
+ 'html'
+ end
+
+ def doctype
+ '<!DOCTYPE html>'.html_safe
+ end
+
+ def to_s
+ doctype + super
+ end
+
+ protected
+
+ def build_head
+ @head = head do
+ meta :"http-equiv" => "Content-type", :content => "text/html; charset=utf-8"
+ end
+ end
+
+ def build_body
+ @body = body
+ end
+ end
+
+ end
+end
View
47 lib/arbre/html/html5_elements.rb
@@ -0,0 +1,47 @@
+module Arbre
+ module HTML
+
+ AUTO_BUILD_ELEMENTS = [ :a, :abbr, :address, :area, :article, :aside, :audio, :b, :base,
+ :bdo, :blockquote, :body, :br, :button, :canvas, :caption, :cite,
+ :code, :col, :colgroup, :command, :datalist, :dd, :del, :details,
+ :dfn, :div, :dl, :dt, :em, :embed, :fieldset, :figcaption, :figure,
+ :footer, :form, :h1, :h2, :h3, :h4, :h5, :h6, :head, :header, :hgroup,
+ :hr, :html, :i, :iframe, :img, :input, :ins, :keygen, :kbd, :label,
+ :legend, :li, :link, :map, :mark, :menu, :meta, :meter, :nav, :noscript,
+ :object, :ol, :optgroup, :option, :output, :pre, :progress, :q,
+ :s, :samp, :script, :section, :select, :small, :source, :span,
+ :strong, :style, :sub, :summary, :sup, :table, :tbody, :td,
+ :textarea, :tfoot, :th, :thead, :time, :title, :tr, :ul, :var, :video ]
+
+ HTML5_ELEMENTS = [ :p ] + AUTO_BUILD_ELEMENTS
+
+ AUTO_BUILD_ELEMENTS.each do |name|
+ class_eval <<-EOF
+ class #{name.to_s.capitalize} < Tag
+ builder_method :#{name}
+ end
+ EOF
+ end
+
+ class P < Tag
+ builder_method :para
+ end
+
+ class Table < Tag
+ def initialize(*)
+ super
+ set_table_tag_defaults
+ end
+
+ protected
+
+ # Set some good defaults for tables
+ def set_table_tag_defaults
+ set_attribute :border, 0
+ set_attribute :cellspacing, 0
+ set_attribute :cellpadding, 0
+ end
+ end
+
+ end
+end
View
175 lib/arbre/html/tag.rb
@@ -0,0 +1,175 @@
+require 'erb'
+
+module Arbre
+ module HTML
+
+ class Tag < Element
+ attr_reader :attributes
+
+ def initialize(*)
+ super
+ @attributes = Attributes.new
+ end
+
+ def build(*args)
+ super
+ attributes = extract_arguments(args)
+ self.content = args.first if args.first
+
+ set_for_attribute(attributes.delete(:for))
+
+ attributes.each do |key, value|
+ set_attribute(key, value)
+ end
+ end
+
+ def extract_arguments(args)
+ if args.last.is_a?(Hash)
+ args.pop
+ else
+ {}
+ end
+ end
+
+ def set_attribute(name, value)
+ @attributes[name.to_sym] = value
+ end
+
+ def get_attribute(name)
+ @attributes[name.to_sym]
+ end
+ alias :attr :get_attribute
+
+ def has_attribute?(name)
+ @attributes.has_key?(name.to_sym)
+ end
+
+ def remove_attribute(name)
+ @attributes.delete(name.to_sym)
+ end
+
+ def id
+ get_attribute(:id)
+ end
+
+ # Generates and id for the object if it doesn't exist already
+ def id!
+ return id if id
+ self.id = object_id.to_s
+ id
+ end
+
+ def id=(id)
+ set_attribute(:id, id)
+ end
+
+ def add_class(class_names)
+ class_list.add class_names
+ end
+
+ def remove_class(class_names)
+ class_list.delete(class_names)
+ end
+
+ # Returns a string of classes
+ def class_names
+ class_list.to_s
+ end
+
+ def class_list
+ get_attribute(:class) || set_attribute(:class, ClassList.new)
+ end
+
+ def to_s
+ indent(opening_tag, content, closing_tag).html_safe
+ end
+
+ private
+
+ def opening_tag
+ "<#{tag_name}#{attributes_html}>"
+ end
+
+ def closing_tag
+ "</#{tag_name}>"
+ end
+
+ INDENT_SIZE = 2
+
+ def indent(open_tag, child_content, close_tag)
+ spaces = ' ' * indent_level * INDENT_SIZE
+
+ html = ""
+
+ if no_child? || child_is_text?
+ if self_closing_tag?
+ html << spaces << open_tag.sub( />$/, '/>' )
+ else
+ # one line
+ html << spaces << open_tag << child_content << close_tag
+ end
+ else
+ # multiple lines
+ html << spaces << open_tag << "\n"
+ html << child_content # the child takes care of its own spaces
+ html << spaces << close_tag
+ end
+
+ html << "\n"
+
+ html
+ end
+
+ def self_closing_tag?
+ %w|meta link|.include?(tag_name)
+ end
+
+ def no_child?
+ children.empty?
+ end
+
+ def child_is_text?
+ children.size == 1 && children.first.is_a?(TextNode)
+ end
+
+
+ def attributes_html
+ attributes.any? ? " " + attributes.to_s : nil
+ end
+
+ def set_for_attribute(record)
+ return unless record
+ # set_attribute :id, ActionController::RecordIdentifier.dom_id(record, default_id_for_prefix)
+ # add_class ActionController::RecordIdentifier.dom_class(record)
+ set_attribute :id, dom_id_for(record)
+ add_class dom_class_name_for(record)
+ end
+
+ def dom_class_name_for(record)
+ if record.class.respond_to?(:model_name)
+ record.class.model_name.singular
+ else
+ record.class.underscore.gsub("/", "_")
+ end
+ end
+
+ def dom_id_for(record)
+ id = if record.respond_to?(:to_key)
+ record.to_key
+ elsif record.respond_to?(:id)
+ record.id
+ else
+ record.object_id
+ end
+
+ [dom_class_name_for(record), id].join("_")
+ end
+
+ def default_id_for_prefix
+ nil
+ end
+
+ end
+
+ end
+end
View
35 lib/arbre/html/text_node.rb
@@ -0,0 +1,35 @@
+require 'erb'
+
+module Arbre
+ module HTML
+
+ class TextNode < Element
+
+ builder_method :text_node
+
+ # Builds a text node from a string
+ def self.from_string(string)
+ node = new
+ node.build(string)
+ node
+ end
+
+ def add_child(*args)
+ raise "TextNodes do not have children"
+ end
+
+ def build(string)
+ @content = string
+ end
+
+ def tag_name
+ nil
+ end
+
+ def to_s
+ ERB::Util.html_escape(@content.to_s)
+ end
+ end
+
+ end
+end
View
229 spec/integration/html_spec.rb
@@ -0,0 +1,229 @@
+require 'spec_helper'
+
+describe Arbre do
+
+ let(:helpers){ nil }
+ let(:assigns){ {} }
+
+ it "should render a single element" do
+ arbre {
+ span "Hello World"
+ }.to_s.should == "<span>Hello World</span>\n"
+ end
+
+ it "should render a child element" do
+ arbre {
+ span do
+ span "Hello World"
+ end
+ }.to_s.should == <<-HTML
+<span>
+ <span>Hello World</span>
+</span>
+HTML
+ end
+
+ it "should render an unordered list" do
+ arbre {
+ ul do
+ li "First"
+ li "Second"
+ li "Third"
+ end
+ }.to_s.should == <<-HTML
+<ul>
+ <li>First</li>
+ <li>Second</li>
+ <li>Third</li>
+</ul>
+HTML
+ end
+
+ it "should allow local variables inside the tags" do
+ arbre {
+ first = "First"
+ second = "Second"
+ ul do
+ li first
+ li second
+ end
+ }.to_s.should == <<-HTML
+<ul>
+ <li>First</li>
+ <li>Second</li>
+</ul>
+HTML
+ end
+
+
+ it "should add children and nested" do
+ arbre {
+ div do
+ ul
+ li do
+ li
+ end
+ end
+ }.to_s.should == <<-HTML
+<div>
+ <ul></ul>
+ <li>
+ <li></li>
+ </li>
+</div>
+HTML
+ end
+
+
+ it "should pass the element in to the block if asked for" do
+ arbre {
+ div do |d|
+ d.ul do
+ li
+ end
+ end
+ }.to_s.should == <<-HTML
+<div>
+ <ul>
+ <li></li>
+ </ul>
+</div>
+HTML
+ end
+
+
+ it "should move content tags between parents" do
+ arbre {
+ div do
+ span(ul(li))
+ end
+ }.to_s.should == <<-HTML
+<div>
+ <span>
+ <ul>
+ <li></li>
+ </ul>
+ </span>
+</div>
+HTML
+ end
+
+ it "should add content to the parent if the element is passed into block" do
+ arbre {
+ div do |d|
+ d.id = "my-tag"
+ ul do
+ li
+ end
+ end
+ }.to_s.should == <<-HTML
+<div id="my-tag">
+ <ul>
+ <li></li>
+ </ul>
+</div>
+HTML
+ end
+
+ it "should have the parent set on it" do
+ arbre {
+ item = nil
+ list = ul do
+ li "Hello"
+ item = li "World"
+ end
+ item.parent.should == list
+ }
+ end
+
+ it "should set a string content return value with no children" do
+ arbre {
+ li do
+ "Hello World"
+ end
+ }.to_s.should == <<-HTML
+<li>Hello World</li>
+HTML
+ end
+
+ it "should turn string return values into text nodes" do
+ arbre {
+ list = li do
+ "Hello World"
+ end
+ node = list.children.first
+ node.class.should == Arbre::HTML::TextNode
+ }
+ end
+
+ describe "self-closing nodes" do
+
+ it "should not self-close script tags" do
+ arbre {
+ script :type => 'text/javascript'
+ }.to_s.should == "<script type=\"text/javascript\"></script>\n"
+ end
+
+ it "should self-close meta tags" do
+ arbre {
+ meta :content => "text/html; charset=utf-8"
+ }.to_s.should == "<meta content=\"text/html; charset=utf-8\"/>\n"
+ end
+
+ it "should self-close link tags" do
+ arbre {
+ link :rel => "stylesheet"
+ }.to_s.should == "<link rel=\"stylesheet\"/>\n"
+ end
+
+ end
+
+ describe "html safe" do
+ it "should escape the contents" do
+ arbre {
+ span("<br />")
+ }.to_s.should == <<-HTML
+<span>&lt;br /&gt;</span>
+HTML
+ end
+
+ it "should return html safe strings" do
+ arbre {
+ span("<br />")
+ }.to_s.should be_html_safe
+ end
+
+ it "should not escape html passed in" do
+ arbre {
+ span(span("<br />"))
+ }.to_s.should == <<-HTML
+<span>
+ <span>&lt;br /&gt;</span>
+</span>
+HTML
+ end
+
+ it "should escape string contents when passed in block" do
+ arbre {
+ span {
+ span {
+ "<br />"
+ }
+ }
+ }.to_s.should == <<-HTML
+<span>
+ <span>&lt;br /&gt;</span>
+</span>
+HTML
+ end
+
+ it "should escape the contents of attributes" do
+ arbre {
+ span(:class => "<br />")
+ }.to_s.should == <<-HTML
+<span class="&lt;br /&gt;"></span>
+HTML
+ end
+ end
+
+end
View
11 spec/spec_helper.rb
@@ -0,0 +1,11 @@
+$LOAD_PATH.unshift(File.dirname(__FILE__))
+$LOAD_PATH << File.expand_path('../support', __FILE__)
+
+require "bundler"
+Bundler.setup
+
+require 'arbre'
+
+def arbre(&block)
+ Arbre::Context.new assigns, helpers, &block
+end
View
23 spec/unit/component.rb
@@ -0,0 +1,23 @@
+require 'spec_helper'
+
+# A mock subclass to play with
+class MockComponent < Arbre::Component; end
+
+describe Arbre::Component do
+
+ let(:component_class){ MockComponent }
+ let(:component){ component_class.new }
+
+ it "should be a subclass of an html div" do
+ Arbre::Component.ancestors.should include(Arbre::HTML::Div)
+ end
+
+ it "should render to a div, even as a subclass" do
+ component.tag_name.should == 'div'
+ end
+
+ it "should add a class by default" do
+ component.class_list.should include("mock_component")
+ end
+
+end
View
35 spec/unit/context_spec.rb
@@ -0,0 +1,35 @@
+# coding: utf-8
+require 'spec_helper'
+
+describe Arbre::Context do
+
+ let(:context) do
+ Arbre::Context.new do
+ h1 "札幌市北区" # Add some HTML to the context
+ end
+ end
+
+ it "should not increment the indent_level" do
+ context.indent_level.should == -1
+ end
+
+ it "should return a bytesize" do
+ context.bytesize.should == 25
+ end
+
+ it "should return a length" do
+ context.length.should == 25
+ end
+
+ it "should delegate missing methods to the html string" do
+ context.should respond_to(:index)
+ context.index('<').should == 0
+ end
+
+ it "should use a cached version of the HTML for method delegation" do
+ context.should_receive(:to_s).once.and_return("<h1>札幌市北区</h1>")
+ context.index('<').should == 0
+ context.index('<').should == 0
+ end
+
+end
View
101 spec/unit/element_finder_methods_spec.rb
@@ -0,0 +1,101 @@
+require 'spec_helper'
+
+describe Arbre::Element, "Finder Methods" do
+ let(:assigns){ {} }
+ let(:helpers){ {} }
+
+ describe "finding elements by tag name" do
+
+ it "should return 0 when no elements exist" do
+ arbre {
+ div
+ }.get_elements_by_tag_name("li").size.should == 0
+ end
+
+ it "should return a child element" do
+ html = arbre do
+ ul
+ li
+ ul
+ end
+ elements = html.get_elements_by_tag_name("li")
+ elements.size.should == 1
+ elements[0].should be_instance_of(Arbre::HTML::Li)
+ end
+
+ it "should return multple child elements" do
+ html = arbre do
+ ul
+ li
+ ul
+ li
+ end
+ elements = html.get_elements_by_tag_name("li")
+ elements.size.should == 2
+ elements[0].should be_instance_of(Arbre::HTML::Li)
+ elements[1].should be_instance_of(Arbre::HTML::Li)
+ end
+
+ it "should return children's child elements" do
+ html = arbre do
+ ul
+ li do
+ li
+ end
+ end
+ elements = html.get_elements_by_tag_name("li")
+ elements.size.should == 2
+ elements[0].should be_instance_of(Arbre::HTML::Li)
+ elements[1].should be_instance_of(Arbre::HTML::Li)
+ elements[1].parent.should == elements[0]
+ end
+ end
+
+ #TODO: describe "finding an element by id"
+
+ describe "finding an element by a class name" do
+
+ it "should return 0 when no elements exist" do
+ arbre {
+ div
+ }.get_elements_by_class_name("my_class").size.should == 0
+ end
+
+ it "should return a child element" do
+ html = arbre do
+ div :class => "some_class"
+ div :class => "my_class"
+ end
+ elements = html.get_elements_by_class_name("my_class")
+ elements.size.should == 1
+ elements[0].should be_instance_of(Arbre::HTML::Div)
+ end
+
+ it "should return multple child elements" do
+ html = arbre do
+ div :class => "some_class"
+ div :class => "my_class"
+ div :class => "my_class"
+ end
+ elements = html.get_elements_by_class_name("my_class")
+ elements.size.should == 2
+ elements[0].should be_instance_of(Arbre::HTML::Div)
+ elements[1].should be_instance_of(Arbre::HTML::Div)
+ end
+
+ it "should return elements that match one of several classes" do
+ html = arbre do
+ div :class => "some_class this_class"
+ div :class => "some_class"
+ div :class => "other_class"
+
+ end
+ elements = html.get_elements_by_class_name("this_class")
+ elements.size.should == 1
+ elements[0].should be_instance_of(Arbre::HTML::Div)
+ end
+
+ # TODO: find children's children by class name
+
+ end
+end
View
251 spec/unit/element_spec.rb
@@ -0,0 +1,251 @@
+require 'spec_helper'
+
+describe Arbre::Element do
+
+ let(:element){ Arbre::Element.new }
+
+ context "when initialized" do
+
+ it "should have no children" do
+ element.children.should be_empty
+ end
+
+ it "should have no parent" do
+ element.parent.should be_nil
+ end
+
+ it "should respond to the HTML builder methods" do
+ element.should respond_to(:span)
+ end
+
+ it "should have a set of local assigns" do
+ element = Arbre::Element.new :hello => "World"
+ element.assigns[:hello].should == "World"
+ end
+
+ it "should have an empty hash with no local assigns" do
+ element.assigns.should == {}
+ end
+
+ end
+
+ describe "passing in a helper object" do
+
+ let(:helper) do
+ Class.new do
+ def helper_method
+ "helper method"
+ end
+ end
+ end
+
+ let(:element){ Arbre::Element.new(nil, helper.new) }
+
+ it "should call methods on the helper object and return TextNode objects" do
+ element.helper_method.should == "helper method"
+ end
+
+ it "should raise a NoMethodError if not found" do
+ lambda {
+ element.a_method_that_doesnt_exist
+ }.should raise_error(NoMethodError)
+ end
+
+ end
+
+ describe "passing in assigns" do
+ let(:post){ stub }
+ let(:assigns){ {:post => post} }
+
+ it "should be accessible via a method call" do
+ element = Arbre::Element.new(assigns)
+ element.post.should == post
+ end
+
+ end
+
+ describe "adding a child" do
+
+ let(:child){ Arbre::Element.new }
+
+ before do
+ element.add_child child
+ end
+
+ it "should add the child to the parent" do
+ element.children.first.should == child
+ end
+
+ it "should set the parent of the child" do
+ child.parent.should == element
+ end
+
+ context "when the child is nil" do
+
+ let(:child){ nil }
+
+ it "should not add the child" do
+ element.children.should be_empty
+ end
+
+ end
+
+ context "when the child is a string" do
+
+ let(:child){ "Hello World" }
+
+ it "should add as a TextNode" do
+ element.children.first.should be_instance_of(Arbre::HTML::TextNode)
+ element.children.first.to_s.should == "Hello World"
+ end
+
+ end
+ end
+
+ describe "setting the content" do
+
+ context "when a string" do
+
+ before do
+ element.add_child "Hello World"
+ element.content = "Goodbye"
+ end
+
+ it "should clear the existing children" do
+ element.children.size.should == 1
+ end
+
+ it "should add the string as a child" do
+ element.children.first.to_s.should == "Goodbye"
+ end
+
+ it "should html escape the string" do
+ string = "Goodbye <br />"
+ element.content = string
+ element.content.to_s.should == "Goodbye &lt;br /&gt;"
+ end
+ end
+
+ context "when an element" do
+ let(:content_element){ Arbre::Element.new }
+
+ before do
+ element.content = content_element
+ end
+
+ it "should set the content tag" do
+ element.children.first.should == content_element
+ end
+
+ it "should set the tags parent" do
+ content_element.parent.should == element
+ end
+ end
+
+ context "when an array of tags" do
+ let(:first){ Arbre::Element.new }
+ let(:second){ Arbre::Element.new }
+
+ before do
+ element.content = [first, second]
+ end
+
+ it "should set the content tag" do
+ element.children.first.should == first
+ end
+
+ it "should set the tags parent" do
+ element.children.first.parent.should == element
+ end
+ end
+
+ end
+
+ describe "rendering to html" do
+
+ it "should render the children collection" do
+ element.children.should_receive(:to_s).and_return("content")
+ element.to_s.should == "content"
+ end
+
+ end
+
+ describe "adding elements together" do
+
+ context "when both elements are tags" do
+ let(:first){ Arbre::Element.new }
+ let(:second){ Arbre::Element.new }
+ let(:collection){ first + second }
+
+ it "should return an instance of Collection" do
+ collection.should be_an_instance_of(Arbre::ElementCollection)
+ end
+
+ it "should return the elements in the collection" do
+ collection.size.should == 2
+ collection.first.should == first
+ collection[1].should == second
+ end
+ end
+
+ context "when the left is a collection and the right is a tag" do
+ let(:first){ Arbre::Element.new }
+ let(:second){ Arbre::Element.new }
+ let(:third){ Arbre::Element.new }
+ let(:collection){ Arbre::ElementCollection.new([first, second]) + third}
+
+ it "should return an instance of Collection" do
+ collection.should be_an_instance_of(Arbre::ElementCollection)
+ end
+
+ it "should return the elements in the collection flattened" do
+ collection.size.should == 3
+ collection[0].should == first
+ collection[1].should == second
+ collection[2].should == third
+ end
+ end
+
+ context "when the right is a collection and the left is a tag" do
+ let(:first){ Arbre::Element.new }
+ let(:second){ Arbre::Element.new }
+ let(:third){ Arbre::Element.new }
+ let(:collection){ first + Arbre::ElementCollection.new([second,third]) }
+
+ it "should return an instance of Collection" do
+ collection.should be_an_instance_of(Arbre::ElementCollection)
+ end
+
+ it "should return the elements in the collection flattened" do
+ collection.size.should == 3
+ collection[0].should == first
+ collection[1].should == second
+ collection[2].should == third
+ end
+ end
+
+ context "when the left is a tag and the right is a string" do
+ let(:element){ Arbre::Element.new }
+ let(:collection){ element + "Hello World"}
+
+ it "should return an instance of Collection" do
+ collection.should be_an_instance_of(Arbre::ElementCollection)
+ end
+
+ it "should return the elements in the collection" do
+ collection.size.should == 2
+ collection[0].should == element
+ collection[1].should be_an_instance_of(Arbre::HTML::TextNode)
+ end
+ end
+
+ context "when the left is a string and the right is a tag" do
+ let(:collection){ "hello World" + Arbre::Element.new}
+
+ it "should return a string" do
+ collection.strip.chomp.should == "hello World"
+ end
+ end
+ end
+
+end
View
60 spec/unit/html/tag_attributes_spec.rb
@@ -0,0 +1,60 @@
+require 'spec_helper'
+
+describe Arbre::HTML::Tag, "Attributes" do
+
+ let(:tag){ Arbre::HTML::Tag.new }
+
+ describe "attributes" do
+
+ before { tag.build :id => "my_id" }
+
+ it "should have an attributes hash" do
+ tag.attributes.should == {:id => "my_id"}
+ end
+
+ it "should render the attributes to html" do
+ tag.to_s.should == <<-HTML
+<tag id="my_id"></tag>
+HTML
+ end
+
+ it "should get an attribute value" do
+ tag.attr(:id).should == "my_id"
+ end
+
+ describe "#has_attribute?" do
+ context "when the attribute exists" do
+ it "should return true" do
+ tag.has_attribute?(:id).should == true
+ end
+ end
+
+ context "when the attribute does not exist" do
+ it "should return false" do
+ tag.has_attribute?(:class).should == false
+ end
+ end
+ end
+
+ it "should remove an attribute" do
+ tag.attributes.should == {:id => "my_id"}
+ tag.remove_attribute(:id).should == "my_id"
+ tag.attributes.should == {}
+ end
+ end
+
+ describe "rendering attributes" do
+ it "should html safe the attribute values" do
+ tag.set_attribute(:class, "\">bad things!")
+ tag.to_s.should == <<-HTML
+<tag class="&quot;&gt;bad things!"></tag>
+HTML
+ end
+ it "should should escape the attribute names" do
+ tag.set_attribute(">bad", "things")
+ tag.to_s.should == <<-HTML
+<tag &gt;bad="things"></tag>
+HTML
+ end
+ end
+end
View
63 spec/unit/html/tag_spec.rb
@@ -0,0 +1,63 @@
+require 'spec_helper'
+
+describe Arbre::HTML::Tag do
+
+ let(:tag){ Arbre::HTML::Tag.new }
+
+ describe "building a new tag" do
+ before { tag.build "Hello World", :id => "my_id" }
+
+ it "should set the contents to a string" do
+ tag.content.should == "Hello World"
+ end
+
+ it "should set the hash of options to the attributes" do
+ tag.attributes.should == { :id => "my_id" }
+ end
+ end
+
+ describe "creating a tag 'for' an object" do
+ let(:model_name){ mock(:singular => "resource_class")}
+ let(:resource_class){ mock(:model_name => model_name) }
+ let(:resource){ mock(:class => resource_class, :to_key => ['5'])}
+
+ before do
+ tag.build :for => resource
+ end
+ it "should set the id to the type and id" do
+ tag.id.should == "resource_class_5"
+ end
+
+ it "should add a class name" do
+ tag.class_list.should include("resource_class")
+ end
+ end
+
+ describe "css class names" do
+
+ it "should add a class" do
+ tag.add_class "hello_world"
+ tag.class_names.should == "hello_world"
+ end
+
+ it "should remove_class" do
+ tag.add_class "hello_world"
+ tag.class_names.should == "hello_world"
+ tag.remove_class "hello_world"
+ tag.class_names.should == ""
+ end
+
+ it "should not add a class if it already exists" do
+ tag.add_class "hello_world"
+ tag.add_class "hello_world"
+ tag.class_names.should == "hello_world"
+ end
+
+ it "should seperate classes with space" do
+ tag.add_class "hello world"
+ tag.class_list.size.should == 2
+ end
+
+ end
+
+end
View
5 spec/unit/html/text_node_spec.rb
@@ -0,0 +1,5 @@
+require 'spec_helper'
+
+describe Arbre::HTML::TextNode do
+
+end
Please sign in to comment.
Something went wrong with that request. Please try again.