Table of Contents

Word documents

Snapshot.VerifyDocxAsync snapshots DOCX files the same way, with the same received and verified files, accept mode and report. It reads the file directly: no Word, Office or Open XML SDK needed.

[Fact]
public async Task Notes_ShouldMatchApprovedSnapshot_WhenGenerated()
{
    using Stream docx = await NotesGenerator.CreateAsync();

    await Snapshot.VerifyDocxAsync(docx);
}

Snapshots are saved as {name}.verified.docx.txt. The Name, Variant and Directory options work as for PDF snapshots, and the document can be a stream, a byte[] or a DocxDocument already loaded, read the same way as a PDF. Snapshot.VerifyDocx is the synchronous version, for a test that isn't async.

What a snapshot looks like

A DOCX snapshot reads like the document: one line per paragraph with its runs merged, and only what differs from its style listed.

# docx-snapshot v1
document
  metadata title="Trail notes" created=yes modified=yes
  settings lang=en-US compat=15 track-changes=no protection=none even-odd-headers=no
  defaults font="Calibri" size=11
sections
  s1 letter margins=72,72,72,72 header=36 footer=36 columns=1 break=nextPage headers=[default]
styles
  ListParagraph paragraph "List Paragraph" font="Arial" size=12 indent-left=36
  TableGrid table "Table Grid" font="Calibri" size=11
numbering
  list 1 definition=1
    level 0 format=decimal text="%1." start=1 indent=36 hanging=18
body
  P style=ListParagraph list=1:0 label="1." "Bring a trail map"
    span "trail" bold
  Table style=TableGrid widths=[144,216]
    Row header
      Cell
        P "Trail"
      Cell
        P "Length"
  P "Visit example.com"
    link "example.com" target="https://example.com" at=6
  P
    image inline size=120x40 alt=(missing) type=png hash=3f9c0a1b2d4e5f60 at=0
headers
  s1 default
    P "Page 3"
      field "PAGE" result="3" at=5
footers
  (none)
notes
  (none)

The Word snapshot format reference lists every section and line.

What's recorded

  • Document settings, each section's page setup, and headers and footers.
  • The styles in use, with their properties resolved through the styles they're based on. Styles nothing uses are left out, since they can't change the document.
  • List labels as Word numbers them.
  • Paragraph layout and text formatting.
  • Tables: grid, header rows, merged cells, shading, widths, margins and borders, alt text, row heights, cell alignment and text direction, and content controls around rows and cells.
  • Images and charts with their alt text and a hash of the image; text boxes with their paragraphs; equations; embedded objects and ActiveX controls.
  • Links, bookmarks, symbols, fields with their results, and form controls.
  • Tracked changes to text, paragraphs and their marks, tables, rows, cells and section settings.
  • Footnotes, endnotes, comments, and inserted documents.

Relationship IDs, editing-session IDs and the zip's layout are left out, so regenerating the same document gives the same snapshot.

Anything PDiffy doesn't read yet, such as a phonetic guide (w:ruby), prints as unsupported <element> hash=..., so a change to it still fails the test.

Leave out what changes

await Snapshot.VerifyDocxAsync(docx, configure: options => options
    // A PAGE field's cached result depends on where Word last laid out the page.
    .Ignore(DocxDetail.FieldResults)
    // The template's styles are tested elsewhere.
    .Ignore(DocxSection.Styles)
    .ScrubDates());

See Ignore and scrub for every option.

Assert on the model

DocxDocument.Load gives the same content as properties:

DocxDocument notes = DocxDocument.Load("notes.docx");

// Every image has alt text.
Assert.All(notes.Images(), image => Assert.NotNull(image.AltText));

// No tracked changes shipped.
Assert.Empty(notes.Revisions());

// Nothing is deleted in a tracked change.
Assert.DoesNotContain(notes.Revisions(), revision => revision.Kind == DocxRevisionKind.Delete);

// The first paragraph uses the Title style.
Assert.Equal("Title", notes.Paragraphs().First().Style);
Member Gives
Paragraphs() Every paragraph in the body, including those in tables, content controls, text boxes and inserted documents
Images() Every image, chart and shape in the body, headers, footers and notes
Revisions() Every tracked change, in reading order
Body, Headers, Footers, Notes The content, as blocks
Styles, Lists, PageSetups Styles in use, lists and section setups
CompareTo(after) A comparison with another document
Check() What the Word checks find, such as an image with no alt text, each with the path a diff shows
ToTranscript() The body as the lines a screen reader announces, in reading order, like a PDF's transcript: headings by outline level, images by alt text (leaving out decorative ones), tables with their header rows, lists and links. pdiffy read notes.docx prints it

Kinds whose values PDiffy defines are enums: DocxRevisionKind, DocxFormKind and DocxNoteKind. Values PDiffy copies from the file stay strings, so a nonstandard value still comes through. Classes such as DocxAlignments (where "both" means justified) and DocxStyleTypes list the standard values, and the API reference lists every such class.

See also