diff --git a/app/decorators/asciicast_decorator.rb b/app/decorators/asciicast_decorator.rb index a4d8b07..97b15fc 100644 --- a/app/decorators/asciicast_decorator.rb +++ b/app/decorators/asciicast_decorator.rb @@ -35,9 +35,9 @@ class AsciicastDecorator < ApplicationDecorator end def thumbnail(width = THUMBNAIL_WIDTH, height = THUMBNAIL_HEIGHT) - snapshot = Snapshot.build(model.snapshot || []) - thumbnail = snapshot.rstrip.crop(width, height).expand(height) - SnapshotPresenter.new(thumbnail).to_html + snapshot = Snapshot.new(model.snapshot || []) + thumbnail = SnapshotDecorator.new(snapshot.thumbnail(width, height)) + h.render 'asciicasts/thumbnail', :thumbnail => thumbnail end def description diff --git a/app/decorators/snapshot_decorator.rb b/app/decorators/snapshot_decorator.rb new file mode 100644 index 0000000..c84c733 --- /dev/null +++ b/app/decorators/snapshot_decorator.rb @@ -0,0 +1,15 @@ +class SnapshotDecorator < ApplicationDecorator + + delegate_all + + def lines + (0...height).map { |line_no| line(line_no) } + end + + private + + def line(line_no) + (0...width).map { |column_no| model.cell(column_no, line_no) } + end + +end diff --git a/app/models/cell.rb b/app/models/cell.rb new file mode 100644 index 0000000..fb32bdf --- /dev/null +++ b/app/models/cell.rb @@ -0,0 +1,22 @@ +class Cell + + attr_reader :text, :brush + + def initialize(text, brush) + @text = text + @brush = brush + end + + def empty? + text.blank? && brush.default? + end + + def ==(other) + text == other.text && brush == other.brush + end + + def css_class + BrushPresenter.new(brush).to_css_class + end + +end diff --git a/app/models/snapshot.rb b/app/models/snapshot.rb index 7591b24..04f2f36 100644 --- a/app/models/snapshot.rb +++ b/app/models/snapshot.rb @@ -1,55 +1,65 @@ class Snapshot - include Enumerable - delegate :each, :to => :lines + attr_reader :width, :height - def self.build(lines) - lines = lines.map { |fragments| SnapshotLine.build(fragments) } - - new(lines) - end - - def initialize(lines = []) - @lines = lines + def initialize(data, raw = true) + @lines = raw ? cellify(data) : data + @width = lines.first && lines.first.size || 0 + @height = lines.size end - def ==(other) - other.lines == lines + def cell(column, line) + lines[line][column] end - def crop(width, height) - min_height = [height, lines.size].min - new_lines = lines.drop(lines.size - min_height).map { |line| - line.crop(width) - } + def thumbnail(width, height) + new_lines = strip_trailing_blank_lines(lines) + new_lines = crop_at_bottom_left(new_lines, width, height) + new_lines = expand(new_lines, width, height) - self.class.new(new_lines) + self.class.new(new_lines, false) end - def rstrip + protected + + def strip_trailing_blank_lines(lines) i = lines.size - 1 - while i >= 0 && lines[i].empty? + while i >= 0 && empty_line?(lines[i]) i -= 1 end - new_lines = i > -1 ? lines[0..i] : [] - - self.class.new(new_lines) + i > -1 ? lines[0..i] : [] end - def expand(height) - new_lines = lines + def crop_at_bottom_left(lines, width, height) + min_height = [height, lines.size].min - while new_lines.size < height - new_lines << [] + lines.drop(lines.size - min_height).map { |line| line.take(width) } + end + + def expand(lines, width, height) + while lines.size < height + lines << [Cell.new(' ', Brush.new)] * width end - self.class.new(new_lines) + lines end - protected + private attr_reader :lines + def cellify(lines) + lines.map { |cells| + cells.map { |cell| + Cell.new(cell[0], Brush.new(cell[1])) + } + } + end + + def empty_line?(cells) + cells.all?(&:empty?) + end + end diff --git a/app/models/snapshot_fragment.rb b/app/models/snapshot_fragment.rb deleted file mode 100644 index adf1626..0000000 --- a/app/models/snapshot_fragment.rb +++ /dev/null @@ -1,28 +0,0 @@ -class SnapshotFragment # TODO: rename to Cell or SnapshotCell - - attr_reader :text, :brush - - delegate :size, :to => :text - - def initialize(text, brush) - @text = text - @brush = brush - end - - def ==(other) - other.text == text && other.brush == brush - end - - def crop(size) - if size >= text.size - self - else - self.class.new(text[0...size], brush) - end - end - - def empty? - text.blank? && brush.default? - end - -end diff --git a/app/models/snapshot_line.rb b/app/models/snapshot_line.rb deleted file mode 100644 index f28aeb3..0000000 --- a/app/models/snapshot_line.rb +++ /dev/null @@ -1,48 +0,0 @@ -class SnapshotLine - include Enumerable - - delegate :each, :to => :fragments - - def self.build(blocks) - fragments = blocks.map { |block| - SnapshotFragment.new(block[0], Brush.new(block[1])) - } - - new(fragments) - end - - def initialize(fragments) - @fragments = fragments - end - - def ==(other) - other.fragments == fragments - end - - def crop(size) - new_fragments = [] - current_size = 0 - - fragments.each do |fragment| - break if current_size == size - - if current_size + fragment.size > size - fragment = fragment.crop(size - current_size) - end - - new_fragments << fragment - current_size += fragment.size - end - - self.class.new(new_fragments) - end - - def empty? - fragments.all?(&:empty?) - end - - protected - - attr_reader :fragments - -end diff --git a/app/presenters/snapshot_fragment_presenter.rb b/app/presenters/snapshot_fragment_presenter.rb deleted file mode 100644 index 6688af4..0000000 --- a/app/presenters/snapshot_fragment_presenter.rb +++ /dev/null @@ -1,15 +0,0 @@ -class SnapshotFragmentPresenter < Draper::Decorator - - delegate :text, :brush - - def to_html - h.content_tag(:span, text, :class => css_class) - end - - private - - def css_class - BrushPresenter.new(brush).to_css_class - end - -end diff --git a/app/presenters/snapshot_line_presenter.rb b/app/presenters/snapshot_line_presenter.rb deleted file mode 100644 index b171ad6..0000000 --- a/app/presenters/snapshot_line_presenter.rb +++ /dev/null @@ -1,19 +0,0 @@ -class SnapshotLinePresenter < Draper::Decorator - - delegate :map - - def to_html - h.content_tag(:span, fragment_strings.html_safe, :class => 'line') - end - - private - - def fragment_strings - map { |fragment| fragment_string(fragment) }.join - end - - def fragment_string(fragment) - SnapshotFragmentPresenter.new(fragment).to_html - end - -end diff --git a/app/presenters/snapshot_presenter.rb b/app/presenters/snapshot_presenter.rb deleted file mode 100644 index d0f466f..0000000 --- a/app/presenters/snapshot_presenter.rb +++ /dev/null @@ -1,19 +0,0 @@ -class SnapshotPresenter < Draper::Decorator - - delegate :map - - def to_html - h.content_tag(:pre, lines_html.html_safe, :class => 'terminal') - end - - private - - def lines_html - map { |line| line_html(line) }.join("\n") + "\n" - end - - def line_html(line) - SnapshotLinePresenter.new(line).to_html - end - -end diff --git a/app/views/asciicasts/_thumbnail.html.slim b/app/views/asciicasts/_thumbnail.html.slim new file mode 100644 index 0000000..71142fd --- /dev/null +++ b/app/views/asciicasts/_thumbnail.html.slim @@ -0,0 +1,6 @@ +pre.terminal + - for line in thumbnail.lines + span.line + - for cell in line + span class=cell.css_class = cell.text + = "\n" diff --git a/spec/decorators/asciicast_decorator_spec.rb b/spec/decorators/asciicast_decorator_spec.rb index 5097f9b..4f688dc 100644 --- a/spec/decorators/asciicast_decorator_spec.rb +++ b/spec/decorators/asciicast_decorator_spec.rb @@ -1,11 +1,12 @@ require 'spec_helper' describe AsciicastDecorator do + include Draper::ViewHelpers let(:asciicast) { Asciicast.new } - let(:decorated) { AsciicastDecorator.new(asciicast) } + let(:decorator) { described_class.new(asciicast) } - subject { decorated.send(method) } + subject { decorator.send(method) } describe '#os' do let(:method) { :os } @@ -136,38 +137,18 @@ describe AsciicastDecorator do describe '#thumbnail' do let(:json) { [:qux] } - let(:snapshot) { double('snapshot') } - let(:presenter) { double('presenter', :to_html => '
') }
+    let(:snapshot) { double('snapshot', :thumbnail => thumbnail) }
+    let(:thumbnail) { double('thumbnail') }
 
     before do
       allow(asciicast).to receive(:snapshot) { json }
-      allow(Snapshot).to receive(:build).with(json) { snapshot }
-      allow(snapshot).to receive(:rstrip) { snapshot }
-      allow(snapshot).to receive(:crop) { snapshot }
-      allow(snapshot).to receive(:expand) { snapshot }
-      allow(SnapshotPresenter).to receive(:new).with(snapshot) { presenter }
+      allow(Snapshot).to receive(:new).with(json) { snapshot }
+      allow(helpers).to receive(:render).
+        with('asciicasts/thumbnail', :thumbnail => thumbnail) { '
' }
     end
 
-    it 'removes empty trailing lines from the snapshot' do
-      decorated.thumbnail(21, 13)
-
-      expect(snapshot).to have_received(:rstrip)
-    end
-
-    it 'crops the snapshot' do
-      decorated.thumbnail(21, 13)
-
-      expect(snapshot).to have_received(:crop).with(21, 13)
-    end
-
-    it 'adds the missing lines to the end of the snapshot' do
-      decorated.thumbnail(21, 13)
-
-      expect(snapshot).to have_received(:expand).with(13)
-    end
-
-    it 'returns html snapshot rendered by SnapshotPresenter#to_html' do
-      expect(decorated.thumbnail).to eq('
')
+    it "returns snapshot's thumbnail rendered by SnapshotPresenter" do
+      expect(decorator.thumbnail).to eq('
')
     end
   end
 
@@ -183,7 +164,7 @@ describe AsciicastDecorator do
       end
 
       it 'returns nickname from decorated user' do
-        decorated.should_receive(:user).twice.and_return(user)
+        decorator.should_receive(:user).twice.and_return(user)
         subject.should == nickname
       end
     end
@@ -219,7 +200,7 @@ describe AsciicastDecorator do
       end
 
       it 'returns link from decorated user' do
-        decorated.should_receive(:user).twice.and_return(user)
+        decorator.should_receive(:user).twice.and_return(user)
         subject.should == link
       end
     end
@@ -232,7 +213,7 @@ describe AsciicastDecorator do
       end
 
       it 'returns author from decorated user' do
-        decorated.should_receive(:author).and_return(author)
+        decorator.should_receive(:author).and_return(author)
         subject.should == author
       end
     end
@@ -250,22 +231,20 @@ describe AsciicastDecorator do
       end
 
       it 'returns img_link from decorated user' do
-        decorated.should_receive(:user).twice.and_return(user)
+        decorator.should_receive(:user).twice.and_return(user)
         subject.should == img_link
       end
     end
 
     context 'when no user present' do
       let(:avatar_image) { double('avatar_image') }
-      let(:h) { double('h') }
 
       before do
         asciicast.user = nil
+        allow(helpers).to receive(:avatar_image_tag).with(nil) { avatar_image }
       end
 
       it 'returns avatar_image_tag' do
-        decorated.stub(:h => h)
-        h.should_receive(:avatar_image_tag).with(nil).and_return(avatar_image)
         subject.should == avatar_image
       end
     end
@@ -291,12 +270,12 @@ describe AsciicastDecorator do
     end
 
     it 'should be an async script tag including asciicast id' do
-      expect(decorated.embed_script).to match(script_regexp)
+      expect(decorator.embed_script).to match(script_regexp)
     end
   end
 
   describe '#duration' do
-    subject { decorated.duration }
+    subject { decorator.duration }
 
     context "when it's below 1 minute" do
       before do
diff --git a/spec/decorators/snapshot_decorator_spec.rb b/spec/decorators/snapshot_decorator_spec.rb
new file mode 100644
index 0000000..927aebf
--- /dev/null
+++ b/spec/decorators/snapshot_decorator_spec.rb
@@ -0,0 +1,22 @@
+require 'spec_helper'
+
+describe SnapshotDecorator do
+
+  let(:decorator) { described_class.new(snapshot) }
+  let(:snapshot) { double('snapshot', :width => 2, :height => 2) }
+  let(:cells) { [
+    [:a, :b],
+    [:c, :d]
+  ] }
+
+  describe '#lines' do
+    subject { decorator.lines }
+
+    before do
+      allow(snapshot).to receive(:cell) { |x, y| cells[y][x] }
+    end
+
+    it { should eq(cells) }
+  end
+
+end
diff --git a/spec/models/cell_spec.rb b/spec/models/cell_spec.rb
new file mode 100644
index 0000000..1703cfd
--- /dev/null
+++ b/spec/models/cell_spec.rb
@@ -0,0 +1,74 @@
+require 'spec_helper'
+
+describe Cell do
+
+  let(:cell) { described_class.new('a', brush) }
+  let(:brush) { double('brush') }
+
+  describe '#empty?' do
+    let(:cell) { described_class.new(text, brush) }
+
+    subject { cell.empty? }
+
+    context "when text is not blank" do
+      let(:text) { 'a' }
+      let(:brush) { double('brush', :default? => true) }
+
+      it { should be(false) }
+    end
+
+    context "when brush is not default" do
+      let(:text) { ' ' }
+      let(:brush) { double('brush', :default? => false) }
+
+      it { should be(false) }
+    end
+
+    context "when text is blank and brush is default" do
+      let(:text) { ' ' }
+      let(:brush) { double('brush', :default? => true) }
+
+      it { should be(true) }
+    end
+  end
+
+  describe '#==' do
+    let(:other) { described_class.new(text, other_brush) }
+
+    subject { cell == other }
+
+    context "when text differs" do
+      let(:text) { 'b' }
+      let(:other_brush) { double('brush', :== => true) }
+
+      it { should be(false) }
+    end
+
+    context "when brush differs" do
+      let(:text) { 'a' }
+      let(:other_brush) { double('brush', :== => false) }
+
+      it { should be(false) }
+    end
+
+    context "when text and brush are equal" do
+      let(:text) { 'a' }
+      let(:other_brush) { double('brush', :== => true) }
+
+      it { should be(true) }
+    end
+  end
+
+  describe '#css_class' do
+    let(:brush_presenter) { double('brush_presenter', :to_css_class => 'kls') }
+
+    subject { cell.css_class }
+
+    before do
+      allow(BrushPresenter).to receive(:new).with(brush) { brush_presenter }
+    end
+
+    it { should eq('kls') }
+  end
+
+end
diff --git a/spec/models/snapshot_fragment_spec.rb b/spec/models/snapshot_fragment_spec.rb
deleted file mode 100644
index 9c5b7aa..0000000
--- a/spec/models/snapshot_fragment_spec.rb
+++ /dev/null
@@ -1,67 +0,0 @@
-require 'spec_helper'
-
-describe SnapshotFragment do
-
-  describe '#==' do
-    let(:snapshot_fragment) { SnapshotFragment.new('foo', brush_1) }
-    let(:brush_1) { double('brush_1') }
-    let(:brush_2) { double('brush_2') }
-
-    subject { snapshot_fragment == other }
-
-    context "when fragments have the same texts and brushes" do
-      let(:other) { SnapshotFragment.new('foo', brush_1) }
-
-      it { should be(true) }
-    end
-
-    context "when fragments have different texts" do
-      let(:other) { SnapshotFragment.new('bar', brush_1) }
-
-      it { should be(false) }
-    end
-
-    context "when fragments have different brushes" do
-      let(:other) { SnapshotFragment.new('foo', brush_2) }
-
-      it { should be(false) }
-    end
-  end
-
-  describe '#crop' do
-    let(:snapshot_fragment) { SnapshotFragment.new('foobar', brush) }
-    let(:brush) { double('brush') }
-
-    context "when size is smaller than fragment's size" do
-      subject { snapshot_fragment.crop(3) }
-
-      it 'returns a new instance of SnapshotFragment' do
-        expect(subject).to be_kind_of(SnapshotFragment)
-        expect(subject).to_not be(snapshot_fragment)
-      end
-
-      it 'trims the text to the requested size' do
-        expect(subject.text).to eq('foo')
-      end
-
-      it 'returns SnapshotFragment with the same brush' do
-        expect(subject.brush).to be(brush)
-      end
-    end
-
-    context "when size is equal or larger than the fragment's size" do
-      it 'returns self' do
-        expect(snapshot_fragment.crop(6)).to be(snapshot_fragment)
-      end
-    end
-  end
-
-  describe '#size' do
-    let(:snapshot_fragment) { SnapshotFragment.new('f' * 100, Brush.new) }
-
-    subject { snapshot_fragment.size }
-
-    it { should eq(100) }
-  end
-
-end
diff --git a/spec/models/snapshot_line_spec.rb b/spec/models/snapshot_line_spec.rb
deleted file mode 100644
index 691f7ac..0000000
--- a/spec/models/snapshot_line_spec.rb
+++ /dev/null
@@ -1,97 +0,0 @@
-require 'spec_helper'
-
-describe SnapshotLine do
-
-  describe '.build' do
-    let(:line) { SnapshotLine.build(input) }
-    let(:input) { [input_fragment_1, input_fragment_2] }
-    let(:input_fragment_1) { ['foo', { :fg => 1, :bold => true }] }
-    let(:input_fragment_2) { ['bar', { :bg => 2 }] }
-
-    it 'returns an instance of SnapshotLine' do
-      expect(line).to be_kind_of(SnapshotLine)
-    end
-
-    it 'returns properly joined fragments' do
-      fragment_1 = line.to_a[0]
-      fragment_2 = line.to_a[1]
-
-      expect(fragment_1.text).to eq('foo')
-      expect(fragment_1.brush).to eq(Brush.new(:fg => 1, :bold => true))
-      expect(fragment_2.text).to eq('bar')
-      expect(fragment_2.brush).to eq(Brush.new(:bg => 2))
-    end
-  end
-
-  describe '#each' do
-    let(:snapshot_line) { SnapshotLine.new([:fragment_1, :fragment_2]) }
-
-    it 'yields to the given block for each fragment' do
-      fragments = []
-
-      snapshot_line.each do |fragment|
-        fragments << fragment
-      end
-
-      expect(fragments).to eq([:fragment_1, :fragment_2])
-    end
-  end
-
-  describe '#==' do
-    let(:snapshot_line) { SnapshotLine.new([:foo]) }
-
-    subject { snapshot_line == other }
-
-    context "when lines have the same fragments" do
-      let(:other) { SnapshotLine.new([:foo]) }
-
-      it { should be(true) }
-    end
-
-    context "when lines have different fragments" do
-      let(:other) { SnapshotLine.new([:foo, :bar]) }
-
-      it { should be(false) }
-    end
-  end
-
-  describe '#crop' do
-    let(:snapshot_line) { SnapshotLine.new(fragments) }
-    let(:fragments) { [fragment_1, fragment_2] }
-    let(:fragment_1) { double('fragment_1', :size => 2, :crop => nil) }
-    let(:fragment_2) { double('fragment_2', :size => 3,
-                                            :crop => cropped_fragment_2) }
-    let(:fragment_3) { double('fragment_3', :size => 4, :crop => nil) }
-    let(:cropped_fragment_2) { double('cropped_fragment_2', :size => 2) }
-
-    context "when cropping point is at the end of the first fragment" do
-      it 'crops none of the fragments' do
-        snapshot_line.crop(2)
-
-        expect(fragment_1).to_not have_received(:crop)
-        expect(fragment_2).to_not have_received(:crop)
-        expect(fragment_3).to_not have_received(:crop)
-      end
-
-      it 'returns a new SnapshotLine with only the first fragment' do
-        expect(snapshot_line.crop(2)).to eq(SnapshotLine.new([fragment_1]))
-      end
-    end
-
-    context "when cropping point is inside of the second fragment" do
-      it 'crops only the second fragment' do
-        snapshot_line.crop(4)
-
-        expect(fragment_1).to_not have_received(:crop)
-        expect(fragment_2).to have_received(:crop).with(2)
-        expect(fragment_3).to_not have_received(:crop)
-      end
-
-      it 'returns a new SnapshotLine with first two fragments cropped' do
-        expect(snapshot_line.crop(4)).
-          to eq(SnapshotLine.new([fragment_1, cropped_fragment_2]))
-      end
-    end
-  end
-
-end
diff --git a/spec/models/snapshot_spec.rb b/spec/models/snapshot_spec.rb
index 6f6dbdb..1fbf227 100644
--- a/spec/models/snapshot_spec.rb
+++ b/spec/models/snapshot_spec.rb
@@ -2,97 +2,101 @@ require 'spec_helper'
 
 describe Snapshot do
 
-  describe '.build' do
-    let(:snapshot) { Snapshot.build(input) }
-    let(:input) { [input_line_1, input_line_2] }
-    let(:input_line_1) { double('input_line_1') }
-    let(:input_line_2) { double('input_line_2') }
-    let(:line_1) { double('line_1') }
-    let(:line_2) { double('line_2') }
-
-    before do
-      allow(SnapshotLine).to receive(:build).with(input_line_1) { line_1 }
-      allow(SnapshotLine).to receive(:build).with(input_line_2) { line_2 }
-    end
+  let(:snapshot) { described_class.new(data) }
 
-    it 'returns an instance of Snapshot' do
-      expect(snapshot).to be_kind_of(Snapshot)
-    end
+  let(:data) { [
+    [['a', fg: 1], ['b', fg: 2], ['c', fg: 3]],
+    [['d', fg: 4], ['e', fg: 5], ['f', fg: 6]],
+    [['g', bg: 1], ['h', bg: 2], ['i', bg: 3]],
+    [[' ', {}   ], ['k', bg: 5], ['l', bg: 6]],
+    [[' ', {}   ], [' ', {}   ], [' ', {}   ]]
+  ] }
 
-    it 'includes lines built by SnapshotLine.build' do
-      expect(snapshot.to_a[0]).to be(line_1)
-      expect(snapshot.to_a[1]).to be(line_2)
-    end
+  describe '#width' do
+    subject { snapshot.width }
+
+    it { should eq(3) }
   end
 
-  describe '#each' do
-    let(:snapshot) { Snapshot.new([:line_1, :line_2]) }
+  describe '#height' do
+    subject { snapshot.height }
+
+    it { should eq(5) }
+  end
 
-    it 'yields to the given block for each line' do
-      lines = []
+  describe '#cell' do
+    subject { snapshot.cell(column, line) }
 
-      snapshot.each do |line|
-        lines << line
-      end
+    context "at 0,0" do
+      let(:column) { 0 }
+      let(:line)   { 0 }
 
-      expect(lines).to eq([:line_1, :line_2])
+      it { should eq(Cell.new('a', Brush.new(fg: 1))) }
     end
-  end
 
-  describe '#==' do
-    let(:snapshot) { Snapshot.new([:foo]) }
+    context "at 1,2" do
+      let(:column) { 1 }
+      let(:line)   { 2 }
 
-    subject { snapshot == other }
+      it { should eq(Cell.new('h', Brush.new(bg: 2))) }
+    end
 
-    context "when the other has the same lines" do
-      let(:other) { Snapshot.new([:foo]) }
+    context "at 2,3" do
+      let(:column) { 2 }
+      let(:line)   { 3 }
 
-      it { should be(true) }
+      it { should eq(Cell.new('l', Brush.new(bg: 6))) }
     end
+  end
 
-    context "when the other has a different lines" do
-      let(:other) { Snapshot.new([:foo, :bar]) }
+  describe '#thumbnail' do
 
-      it { should be(false) }
+    def thumbnail_text(thumbnail)
+      ''.tap do |text|
+        0.upto(thumbnail.height - 1) do |line|
+          0.upto(thumbnail.width - 1) do |column|
+            text << thumbnail.cell(column, line).text
+          end
+          text << "\n"
+        end
+      end
     end
-  end
 
-  describe '#crop' do
-    let(:snapshot) { Snapshot.new(lines) }
-    let(:lines) { [line_1, line_2, line_3] }
-    let(:line_1) { double('line_1', :crop => cropped_line_1) }
-    let(:line_2) { double('line_2', :crop => cropped_line_2) }
-    let(:line_3) { double('line_3', :crop => cropped_line_3) }
-    let(:cropped_line_1) { double('cropped_line_1') }
-    let(:cropped_line_2) { double('cropped_line_2') }
-    let(:cropped_line_3) { double('cropped_line_3') }
-    let(:width) { 3 }
+    let(:height) { 3 }
+    let(:thumbnail) { snapshot.thumbnail(2, height) }
+    let(:text) { thumbnail_text(thumbnail) }
 
-    subject { snapshot.crop(width, height) }
+    it 'is a snapshot of requested width' do
+      expect(thumbnail.width).to eq(2)
+    end
 
-    context "when height is lower than lines count" do
-      let(:height) { 2 }
+    it 'is a snapshot of requested height' do
+      expect(thumbnail.height).to eq(3)
+    end
 
-      it 'crops the last "height" lines' do
-        subject
+    context "when height is 3" do
+      let(:height) { 3 }
 
-        expect(line_1).to_not have_received(:crop)
-        expect(line_2).to have_received(:crop).with(3)
-        expect(line_3).to have_received(:crop).with(3)
+      it 'returns thumbnail with 2nd, 3rd and 4th line cropped' do
+        expect(text).to eq("de\ngh\n k\n")
       end
+    end
+
+    context "when height is 5" do
+      let(:height) { 5 }
 
-      it 'returns a new Snapshot with last 2 lines cropped' do
-        expect(subject).to eq(Snapshot.new([cropped_line_2, cropped_line_3]))
+      it 'returns thumbnail with all the lines cropped' do
+        expect(text).to eq("ab\nde\ngh\n k\n  \n")
       end
     end
 
-    context "when height is equal to lines count" do
-      let(:height) { 3 }
+    context "when height is 6" do
+      let(:height) { 6 }
 
-      it 'returns a new Snapshot with all lines cropped' do
-        expect(subject).to eq(Snapshot.new([cropped_line_1, cropped_line_2,
-                                            cropped_line_3]))
+      it 'returns thumbnail with all the lines cropped + 1 empty line' do
+        expect(text).to eq("ab\nde\ngh\n k\n  \n  \n")
       end
     end
   end
+
 end
diff --git a/spec/presenters/snapshot_fragment_presenter_spec.rb b/spec/presenters/snapshot_fragment_presenter_spec.rb
deleted file mode 100644
index a1ae788..0000000
--- a/spec/presenters/snapshot_fragment_presenter_spec.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-require 'spec_helper'
-
-describe SnapshotFragmentPresenter do
-  let(:snapshot_fragment_presenter) { described_class.new(snapshot_fragment) }
-  let(:snapshot_fragment) { SnapshotFragment.new('foo > bar', brush) }
-  let(:brush) { double('brush') }
-  let(:brush_presenter) { double('presenter', :to_css_class => css_class) }
-  let(:css_class) { 'qux' }
-
-  describe '#to_html' do
-    subject { snapshot_fragment_presenter.to_html }
-
-    before do
-      allow(BrushPresenter).to receive(:new).with(brush).
-        and_return(brush_presenter)
-    end
-
-    it { should be_kind_of(ActiveSupport::SafeBuffer) }
-    it { should eq('foo > bar') }
-
-    context "when css class is nil" do
-      let(:css_class) { nil }
-
-      it { should eq('foo > bar') }
-    end
-  end
-end
diff --git a/spec/presenters/snapshot_line_presenter_spec.rb b/spec/presenters/snapshot_line_presenter_spec.rb
deleted file mode 100644
index 5d14c39..0000000
--- a/spec/presenters/snapshot_line_presenter_spec.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-require 'spec_helper'
-
-describe SnapshotLinePresenter do
-  let(:snapshot_line_presenter) { SnapshotLinePresenter.new(snapshot_line) }
-  let(:snapshot_line) { SnapshotLine.new([fragment_1, fragment_2]) }
-  let(:fragment_1) { double('fragment_1') }
-  let(:fragment_2) { double('fragment_2') }
-  let(:fragment_1_presenter) { double(:to_html => '') }
-  let(:fragment_2_presenter) { double(:to_html => '') }
-
-  describe '#to_html' do
-    subject { snapshot_line_presenter.to_html }
-
-    before do
-      allow(SnapshotFragmentPresenter).to receive(:new).with(fragment_1).
-        and_return(fragment_1_presenter)
-      allow(SnapshotFragmentPresenter).to receive(:new).with(fragment_2).
-        and_return(fragment_2_presenter)
-    end
-
-    it { should be_kind_of(ActiveSupport::SafeBuffer) }
-    it { should eq('') }
-  end
-end
diff --git a/spec/presenters/snapshot_presenter_spec.rb b/spec/presenters/snapshot_presenter_spec.rb
deleted file mode 100644
index ba62c18..0000000
--- a/spec/presenters/snapshot_presenter_spec.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-require 'spec_helper'
-
-describe SnapshotPresenter do
-  let(:snapshot_presenter) { SnapshotPresenter.new(snapshot) }
-  let(:snapshot) { Snapshot.new([line_1, line_2]) }
-  let(:line_1) { double('line_1') }
-  let(:line_2) { double('line_2') }
-  let(:line_1_presenter) { double(:to_html => '') }
-  let(:line_2_presenter) { double(:to_html => '') }
-
-  describe '#to_html' do
-    subject { snapshot_presenter.to_html }
-
-    before do
-      allow(SnapshotLinePresenter).to receive(:new).with(line_1).
-        and_return(line_1_presenter)
-      allow(SnapshotLinePresenter).to receive(:new).with(line_2).
-        and_return(line_2_presenter)
-    end
-
-    it { should be_kind_of(ActiveSupport::SafeBuffer) }
-    it { should eq(%(
\n\n
)) } - end -end