76fd08ed82
Change-Id: I8170f95e01b98db2fe79070dacfd7436e37019a2
242 lines
9.5 KiB
Text
242 lines
9.5 KiB
Text
= 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" );
|