The case of
HoldForm appears to by design, although the motivation for that design is unclear.
HoldForm should thus be listed as one of the special heads listed in the
TextString documentation. The case of
Hold appears to be an evaluation leak, but maybe it is intentional for symmetry with
A trace of the
HoldForm variant suggests that the behaviour is by design. Here are selected evaluations from the trace:
ostr helper function has definitions that explicitly strip
DownValues // Select[Not@*FreeQ[HoldForm]] // PrettyForm
The documentation for
TextString lists a number of heads that are treated specially -- apparently
HoldForm should be added to that list.
The case of the
Hold variant is not so clear. Here is a selective trace:
There are no definitions under any of the internal helper functions that explicitly deal with
Hold. A close inspection of the trace yields use of
Unevaluated, which suggests that some attempt was made to prevent evaluation. But this measure is defeated by later steps.
My guess is that there is an evaluation leak here, but perhaps it is just a happy(?) accident that results in a treatment for
Hold that matches that of
HoldForm be stripped when converting an expression to a string? I have no idea. It would seem that the ability to convert unevaluated expressions to strings would be valuable. If it weren't for the explicit definitions on
ostr, I would have called this a bug. Now I guess I have to call it an inscrutable design decision.
(This analysis is current as of Mathematica 11.2.0.)