office-gobmx/sw/qa/extras
Miklos Vajna dd11bca8ab n#777337 testcase
Also remove the #if 0 I forgot to remove previously.

Change-Id: I2cc5e1407c509414c8395d0fd63ab3b379bef04f
2012-08-30 09:12:12 +02:00
..
data/odt
inc
odfexport
odfimport
ooxmlexport
ooxmlimport n#777337 testcase 2012-08-30 09:12:12 +02:00
rtfexport
rtfimport fdo#52052 fix RTF import of page breaks on landscape pages 2012-08-28 11:54:37 +02:00
ww8export
ww8import
README
regression-test.cxx
swmodeltestbase.hxx SwModelTestBase: support extracting node contents from a layout dump 2012-08-28 11:54:37 +02:00

= How to add a new Writer filter test

The `sw/qa/extras/` subdirectory has multiple import and export filter unit
tests. This file documents how to add new testcases to this framework.

== Import tests

Import tests are the easier ones. All start with a `load()` method that loads a
file to `mxComponent`, which represents the UNO model of the document.

The rest of the testcase is about asserting this document model: use the UNO
API to retrieve properties, then use `CPPUNIT_ASSERT_EQUAL()` to test against
an expected value.

See below for more details on writing the UNO code see below.

== Export tests

Export tests are similar. Given that test documents are easier to provide in
some format (instead of writing code to build the documents from scratch) in
most cases, we will do an import, then do an export (to invoke the code we want
to test) and then do an import again, so we can do the testing by asserting the
document model, just like we did for import tests.

Yes, this means that you can test the export code (using this framework) if the
importer is working correctly. (But that's not so bad, users usually expect a
feature to work in both the importer and the exporter.)

The only difference is that instead of `load()`, you call `roundtrip()` to load
the test document, then you can assert it as discussed above.

== Helper methods

When two or more tests do the same (for example determine the number of
characters in the document), helper methods are introduced to avoid code
duplication. When you need something more complex, check if there is already a
helper method, they are also good examples.

Helper methods which are used by more than one testsuite are in the
`SwModelTestBase` class. For example the `getLength()` method uses the trick
that you can simply enumerate over the document model, getting the paragraphs
of it; and inside those, you can enumerate over their runs. That alone is
enough if you want to test a paragraph or character property.

== Using UNO for tests

Figuring out the UNO API just by reading the idl files under `offapi/` is not
that productive. Xray can help in this case. Download it from:

http://bernard.marcelly.perso.sfr.fr/index2.html

It's an SXW file, start Writer, Tools -> Options -> LibreOffice -> Security,
Macro Security, and there choose Low. Then open the SXW, and click `Install
Xray`. Now you can close the SXW. Open your testcase, which is imported
correctly (from a fixed bugs's point of view). Then open the basic editor
(Tools -> Macros -> LibreOffice Basic -> Organize Macros, Edit), and start to
write your testcase as `Sub Main`. You don't have to know much about basic, for
a typical testcase you need no `if`, `for`, or anything like that.

NOTE: Once you restart Writer, xray will no longer be loaded automatically. For
subsequent starts, place the following line in `Main` before you do anything
else:

----
GlobalScope.BasicLibraries.LoadLibrary("XrayTool")
----

The above `mxComponent` is available as `ThisComponent` in basic, and if you
want to inspect a variable here, you can use the `xray` command to inspect
properties, methods, interfaces, etc.

Let's take for example fdo#49501. The problem there was the page was not
landscape (and a few more, let's ignore that).

You can start with:

----
xray ThisComponent
----

and navigate around (it is a good idea to click Configuration and enable
alphabetical sorting). The good thing is that once you write the code, you can
just start F5 without restarting LibreOffice to see the result, so you can
develop quickly.

With some experimenting, you'll end up with something like this:

----
oStyle = ThisComponent.StyleFamilies.PageStyles.Default
xray oStyle.IsLandscape
----

Now all left is to rewrite that in cpp, where it'll be much easier to debug
when later this test fails for some reason. In cpp, you typically need to be
more verbose, so the code will look like:

----
uno::Reference<style::XStyleFamiliesSupplier> xStyleFamiliesSupplier(mxComponent, uno::UNO_QUERY);
uno::Reference<container::XNameAccess> xStyles(xStyleFamiliesSupplier->getStyleFamilies(), uno::UNO_QUERY);
uno::Reference<container::XNameAccess> xPageStyles(xStyles->getByName("PageStyles"), uno::UNO_QUERY);
uno::Reference<beans::XPropertySet> xStyle(xPageStyles->getByName("Default"), uno::UNO_QUERY);

sal_Bool bIsLandscape = sal_False;
xStyle->getPropertyValue("IsLandscape") >>= bIsLandscape;
CPPUNIT_ASSERT_EQUAL(sal_True, bIsLandscape);
----

== UNO, in more details, various tips:

=== writing code based xray inspection:

In general, if you want to access a property, in Basic it's enough to write 'object.property',
such as printing character count that 'xray ThisComponent' prints as 'CharacterCount':

count = ThisComponent.CharacterCount
text = paragraph.String

In C++, this can get more complicated, as you need to use the right interface for access. Xray
prints the internal name of the object (e.g. 'SwXTextDocument' for 'xray ThisComponent')
above the list of its properties. Inspect this class/interface in the code (that is,
under offapi/, udkapi/, or wherever it is implemented) and search for a function named
similarly to the property you want (getXYZ()). If there is none, it is most
probably a property that can be read using XPropertySet or using the getProperty helper:

sal_Int32 val = getProperty< sal_Int32 >( textDocument, "CharacterCount" );

If there is a function to obtain the property, you need access it using the right interface.
If the class itself is not the right interface, then it is one of the classes it inherits
from, usually the block of functions that are implemented for this interface starts with
stating the name. For example see sw/inc/unoparagraph.hxx for class SwXParagraph, it has
function getString() in a block introduced with 'XTextRange', so XTextRange is the interface
it inherits from:

// text of the paragraph
uno::Reference<text::XTextRange> text(paragraph, uno::UNO_QUERY);
OUString value = text->getString();

Some properties may be more complicated to access, such as using XEnumerationAccess, XIndexAccess
or XNamedAccess to enumerate items, index them by number of name (clicking 'Dbg_SupportedInterfaces'
in xray gives a list of interfaces the object implements, and 'Count' shows the number of items).

=== XEnumerationAccess (e.g. get the 2nd paragraph of the document):

Basic:

enum = ThisComponent.Text.createEnumeration
para = enum.NextElement
para = enum.NextElement
xray para

C++:

uno::Reference<text::XTextDocument> textDocument(mxComponent, uno::UNO_QUERY);
uno::Reference<container::XEnumerationAccess> paraEnumAccess(textDocument->getText(), uno::UNO_QUERY);
// list of paragraphs
uno::Reference<container::XEnumeration> paraEnum = paraEnumAccess->createEnumeration();
// go to 1st paragraph
(void) paraEnum->nextElement();
// get the 2nd paragraph
uno::Reference<uno::XInterface> paragraph(paraEnum->nextElement(), uno::UNO_QUERY);

Note that for paragraphs it's easier to use getParagraph(), which gets the given
paragraph (counted from 1) and optionally checks the paragraph text.

uno::Reference< text::XTextRange > paragraph = getParagraph( 2, "TEXT" )

=== XNamedAccess (e.g. get a bookmark named 'position1'):

Basic:

bookmark = ThisComponent.Bookmarks.getByName("position1")

or even simpler

bookmark = ThisComponent.Bookmarks.position1

C++:

uno::Reference<text::XTextDocument> textDocument(mxComponent, uno::UNO_QUERY);
// XBookmarksSupplier interface will be needed to access the bookmarks
uno::Reference<text::XBookmarksSupplier> bookmarksSupplier(textDocument, uno::UNO_QUERY);
// get the bookmarks
uno::Reference<container::XNameAccess> bookmarks(bookmarksSupplier->getBookmarks(), uno::UNO_QUERY);
uno::Reference<uno::XInterface> bookmark;
// get the bookmark by name
bookmarks->getByName("position1") >>= bookmark;

=== XIndexAccess (e.g. get the first bookmark):

Basic:

bookmark = ThisComponent.Bookmarks.getByIndex(0)

C++:

uno::Reference<text::XTextDocument> textDocument(mxComponent, uno::UNO_QUERY);
// XBookmarksSupplier interface will be needed to access the bookmarks
uno::Reference<text::XBookmarksSupplier> bookmarksSupplier(textDocument, uno::UNO_QUERY);
// get the bookmarks
uno::Reference<container::XIndexAccess> bookmarks(bookmarksSupplier->getBookmarks(), uno::UNO_QUERY);
uno::Reference<uno::XInterface> bookmark;
// get the bookmark by index
bookmarks->getByIndex(0) >>= bookmark;

=== Images

Embedded images seem to be accessed like this:

Basic:

image = ThisComponent.DrawPage.getByIndex(0)
graphic = image.Graphic

C++:

uno::Reference<text::XTextDocument> textDocument(mxComponent, uno::UNO_QUERY);
uno::Reference<drawing::XDrawPageSupplier> drawPageSupplier(textDocument, uno::UNO_QUERY);
uno::Reference<drawing::XDrawPage> drawPage = drawPageSupplier->getDrawPage();
uno::Reference<drawing::XShape> image;
drawPage->getByIndex(0) >>= image;
uno::Reference<graphic::XGraphic> graphic = getProperty< uno::Reference< graphic::XGraphic > >( image, "Graphic" );


=== Styles

Styles provide information about many properties of (parts of) the document, for example
page width:

Basic:

ThisComponent.StyleFamilies.PageStyles.Default.Width

C++:

uno::Reference<text::XTextDocument> textDocument(mxComponent, uno::UNO_QUERY);
uno::Reference<style::XStyleFamiliesSupplier> styleFamiliesSupplier(mxComponent, uno::UNO_QUERY);
uno::Reference<container::XNameAccess> styleFamilies = styleFamiliesSupplier->getStyleFamilies();
uno::Reference<container::XNameAccess> pageStyles;
styleFamilies->getByName("PageStyles") >>= pageStyles;
uno::Reference<uno::XInterface> defaultStyle;
pageStyles->getByName("Default") >>= defaultStyle;
sal_Int32 width = getProperty< sal_Int32 >( defaultStyle, "Width" );