 
   equal @text.getLength(), 17, "multiple-run text length"
 
+
+# Test helpers
+
+runsEqual = (text, expectedRuns, message) ->
+  actualRuns = getRunsForText(text)
+
+  if expectedRuns.length isnt actualRuns.length
+    return QUnit.push(false, actualRuns.length, expectedRuns.length, "#{message} (run length mismatch)")
+
+  for actualRun, index in actualRuns
+    expectedRun = expectedRuns[index]
+
+    if expectedRun.attachment? and expectedRun.attachment isnt actualRun.attachment
+      return QUnit.push(false, actualRun.attachment, expectedRun.attachment, "#{message} (attachment mismatch in run #{index + 1})")
+
+    if expectedRun.attributes? and not Trix.Hash.box(expectedRun.attributes).isEqualTo(actualRun.attributes)
+      return QUnit.push(false, actualRun.attributes, expectedRun.attributes, "#{message} (attributes mismatch in run #{index + 1})")
+
+    if expectedRun.string? and expectedRun.string isnt actualRun.string
+      return QUnit.push(false, actualRun.string, expectedRun.string, "#{message} (string mismatch in run #{index + 1})")
+
+  QUnit.push(true, actualRuns, expectedRuns, message)
+
+getRunsForText = (text) ->
+  result = []
+  text.eachRun (run) -> result.push(run)
+  result

      new Trix.Piece("text", { italic: true }),
      new Trix.Piece("!")
    ]

test "#isEqualTo", ->
  ok @text.isEqualTo(@text), "text is equal to itself"

  text = @text.getTextAtRange([0, 16])
  ok !@text.isEqualTo(text) and !text.isEqualTo(@text), "text is not equal to subslice of text"


test "#getLength", ->
  text = new Trix.Text
  equal text.getLength(), 0, "empty text length is 0"
