format: an elided line keeps its parameters' quoting
Format.elide_line rebuilt a long line's header by hand, dropping the
quotes around a parameter value holding `:`, `;` or `,`, so the line
on screen read as different parameters than the card holds. It now
rebuilds through VCard.header_of, the writer's own spelling.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VAiyEheRBdzbBjZUDWZUPk
change
commit 6655525fe6213140a9c17a18c4505125e95bc22d
author Claude <noreply@anthropic.com>
date
parent 082b66d0
diff --git a/lib/pro_tacts/admin/format.rb b/lib/pro_tacts/admin/format.rb
index bafd033..7ada939 100644
--- a/lib/pro_tacts/admin/format.rb
+++ b/lib/pro_tacts/admin/format.rb
@@ -125,8 +125,9 @@ module ProTacts
       # One line of the display card: verbatim when nothing in it runs
       # past ELIDE_ABOVE; rebuilt from its parse when something does,
       # each long value standing in as its count. The rebuild is the
-      # parser's spelling rather than the bytes' — an elided line is a
-      # rendering — and the line keeps its own terminator so the
+      # writer's spelling rather than the bytes' (VCard.header_of, which
+      # quotes a parameter value PTEXT cannot hold) — an elided line is
+      # a rendering — and the line keeps its own terminator so the
       # card's line-break convention survives.
       #: (VCard::Parser::Line line) -> String
       def self.elide_line(line)
@@ -137,10 +138,9 @@ module ProTacts
           property.value.bytesize > ELIDE_ABOVE
         return line.verbatim unless long
 
-        header = property.group ? "#{property.group}.#{property.name}" : property.name
-        params = property.parameters.map { |name, value| "#{name}=#{count_octets(value)}" }
+        elided = property.with(parameters: property.parameters.map { |name, value| [name, count_octets(value)] })
         terminator = line.verbatim[/\r?\n\z/] || "\r\n"
-        "#{[header, *params].join(';')}:#{count_octets(property.value)}#{terminator}"
+        "#{VCard.header_of(elided)}#{count_octets(property.value)}#{terminator}"
       end
 
       # A line that would not parse has no structure to rebuild from,
diff --git a/test/pro_tacts/admin/test_format.rb b/test/pro_tacts/admin/test_format.rb
index 5a2abf6..8062baf 100644
--- a/test/pro_tacts/admin/test_format.rb
+++ b/test/pro_tacts/admin/test_format.rb
@@ -197,4 +197,32 @@ class FormatTest < Minitest::Test
       assert_equal key, ProTacts::Admin::Format.search_key(key)
     end
   end
+
+  ## Eliding a line
+
+  # An elided line still reads as the line it stands for: its group,
+  # its name, and each parameter, a long value as its count and every
+  # other as it was — quoted where the grammar must quote it, so a
+  # parameter holding `:` or `,` is not cut apart on the way to the
+  # screen.
+  def test_an_elided_line_reads_as_its_line_with_long_values_counted
+    token = from_regex("[A-Za-z0-9-]{1,8}", fullmatch: true)
+    short = from_regex('[^\x00-\x08\x0a-\x1f\x7f"]{0,12}', fullmatch: true)
+    long = from_regex("[a-z:,;]{121,140}", fullmatch: true)
+    Hegel.test(test_cases: 1_000) do |tc|
+      property = ProTacts::VCard::Parser::Property.new(
+        group: tc.draw(optional(token)),
+        name: tc.draw(token),
+        parameters: tc.draw(arrays(tuples(token, one_of(short, long)), max_size: 3)),
+        value: tc.draw(one_of(short.map { it.delete(":;,\\") }, long)),
+      )
+      line = ProTacts::VCard.new("#{ProTacts::VCard.header_of(property)}#{property.value}\r\n").lines.fetch(0)
+      count = ->(value) { value.bytesize > ProTacts::Admin::Format::ELIDE_ABOVE ? "[#{value.bytesize} octets elided]" : value }
+
+      elided = ProTacts::VCard.new(ProTacts::Admin::Format.elide_line(line)).lines.fetch(0).property
+
+      assert_equal property.with(parameters: property.parameters.map { [it[0], count.(it[1])] }, value: count.(property.value)),
+                   elided
+    end
+  end
 end