= 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. First you need to add a new entry to the table inside the `run()` method, so the framework will load the specified file to `mxComponent`, which represents the UNO model of the document. The rest of the testcase is about implementing the test method 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 in these tests the test method is called twice: once after the initial import -- so you can see if the export fails due to an import problem in fact -- and once after the export and import. The test method should still assert the document model only, 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 xStyleFamiliesSupplier(mxComponent, uno::UNO_QUERY); uno::Reference xStyles(xStyleFamiliesSupplier->getStyleFamilies(), uno::UNO_QUERY); uno::Reference xPageStyles(xStyles->getByName("PageStyles"), uno::UNO_QUERY); uno::Reference xStyle(xPageStyles->getByName(DEFAULT_STYLE), 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(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 textDocument(mxComponent, uno::UNO_QUERY); uno::Reference paraEnumAccess(textDocument->getText(), uno::UNO_QUERY); // list of paragraphs uno::Reference paraEnum = paraEnumAccess->createEnumeration(); // go to 1st paragraph (void) paraEnum->nextElement(); // get the 2nd paragraph uno::Reference 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 textDocument(mxComponent, uno::UNO_QUERY); // XBookmarksSupplier interface will be needed to access the bookmarks uno::Reference bookmarksSupplier(textDocument, uno::UNO_QUERY); // get the bookmarks uno::Reference bookmarks(bookmarksSupplier->getBookmarks(), uno::UNO_QUERY); uno::Reference 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 textDocument(mxComponent, uno::UNO_QUERY); // XBookmarksSupplier interface will be needed to access the bookmarks uno::Reference bookmarksSupplier(textDocument, uno::UNO_QUERY); // get the bookmarks uno::Reference bookmarks(bookmarksSupplier->getBookmarks(), uno::UNO_QUERY); uno::Reference 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 textDocument(mxComponent, uno::UNO_QUERY); uno::Reference drawPageSupplier(textDocument, uno::UNO_QUERY); uno::Reference drawPage = drawPageSupplier->getDrawPage(); uno::Reference image; drawPage->getByIndex(0) >>= image; uno::Reference 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 textDocument(mxComponent, uno::UNO_QUERY); uno::Reference styleFamiliesSupplier(mxComponent, uno::UNO_QUERY); uno::Reference styleFamilies = styleFamiliesSupplier->getStyleFamilies(); uno::Reference pageStyles; styleFamilies->getByName("PageStyles") >>= pageStyles; uno::Reference defaultStyle; pageStyles->getByName(DEFAULT_STYLE) >>= defaultStyle; sal_Int32 width = getProperty< sal_Int32 >( defaultStyle, "Width" );